diff --git a/src/interpreter/classes.rs b/src/interpreter/classes.rs
index a782023..494d0a8 100644
--- a/src/interpreter/classes.rs
+++ b/src/interpreter/classes.rs
@@ -2,7 +2,7 @@ use std::{cell::RefCell, collections::HashMap, fmt::Display, rc::Rc};
 
 use crate::{
     errors::{ErrorKind, SloxError, SloxResult},
-    tokens::Token,
+    tokens::{Token, TokenType},
 };
 
 use super::{functions::Function, Callable, InterpreterState, Value};
@@ -24,6 +24,13 @@ pub struct Instance {
     fields: HashMap<String, Value>,
 }
 
+/// A method bound to an instance.
+#[derive(Debug, Clone)]
+pub struct BoundMethod {
+    instance: Value,
+    method: String,
+}
+
 /* -------------------- *
  * Class implementation *
  * -------------------- */
@@ -88,6 +95,18 @@ impl Instance {
     pub(super) fn set(&mut self, name: &Token, value: Value) {
         self.fields.insert(name.lexeme.clone(), value);
     }
+
+    fn with_method<F, Rt>(&self, name: &str, f: F) -> Rt
+    where
+        F: FnOnce(&Function) -> Rt,
+    {
+        let cls = self.class.borrow();
+        let method = cls
+            .methods
+            .get(name)
+            .expect(&format!("Method {} not found", name));
+        f(method)
+    }
 }
 
 impl Display for Instance {
@@ -95,3 +114,42 @@ impl Display for Instance {
         f.write_fmt(format_args!("<Instance of {}>", self.class.borrow(),))
     }
 }
+
+/* --------------------------- *
+ * Bound method implementation *
+ * --------------------------- */
+
+impl BoundMethod {
+    fn with_method<F, Rt>(&self, f: F) -> Rt
+    where
+        F: FnOnce(&Function) -> Rt,
+    {
+        self.instance.with_instance(
+            |instance| instance.with_method(&self.method, f),
+            || panic!("Instance value does not contain an instance"),
+        )
+    }
+
+    fn this_token(&self) -> Token {
+        Token {
+            token_type: TokenType::This,
+            lexeme: "this".to_owned(),
+            line: self.with_method(|method| method.name().expect("Method has no name").line),
+        }
+    }
+}
+
+impl Callable for BoundMethod {
+    fn arity(&self) -> usize {
+        self.with_method(|m| m.arity())
+    }
+
+    fn call(&self, itpr_state: &mut InterpreterState, arguments: Vec<Value>) -> SloxResult<Value> {
+        let mut this_env = InterpreterState::create_child(itpr_state);
+        this_env
+            .environment
+            .borrow_mut()
+            .define(&self.this_token(), Some(self.instance.clone()))?;
+        self.with_method(|m| m.call(&mut this_env, arguments))
+    }
+}
diff --git a/src/interpreter/functions.rs b/src/interpreter/functions.rs
index cac76bd..1813719 100644
--- a/src/interpreter/functions.rs
+++ b/src/interpreter/functions.rs
@@ -31,6 +31,10 @@ impl Function {
             env: environment,
         }
     }
+
+    pub(super) fn name(&self) -> Option<&Token> {
+        self.name.as_ref()
+    }
 }
 
 impl Callable for Function {