diff --git a/src/slices-and-lifetimes/exercise.md b/src/slices-and-lifetimes/exercise.md
index f46c314c..a7b522fb 100644
--- a/src/slices-and-lifetimes/exercise.md
+++ b/src/slices-and-lifetimes/exercise.md
@@ -39,7 +39,8 @@ called VARINT. Luckily, `parse_varint` is defined for you below. The given code
 also defines callbacks to handle `Person` and `PhoneNumber` fields, and to parse
 a message into a series of calls to those callbacks.
 
-What remains for you is to implement the `parse_field` function.
+What remains for you is to implement the `parse_field` function and the
+`ProtoMessage` trait for `Person` and `PhoneNumber`.
 
 <!-- compile_fail because `mdbook test` does not allow use of `thiserror` -->
 ```rust,editable,compile_fail
@@ -56,5 +57,9 @@ What remains for you is to implement the `parse_field` function.
 
 {{#include exercise.rs:parse_message }}
 
+{{#include exercise.rs:message_types}}
+
+// TODO: Implement ProtoMessage for Person and PhoneNumber.
+
 {{#include exercise.rs:main }}
 ```
diff --git a/src/slices-and-lifetimes/exercise.rs b/src/slices-and-lifetimes/exercise.rs
index cea0f4e6..6a59f8c5 100644
--- a/src/slices-and-lifetimes/exercise.rs
+++ b/src/slices-and-lifetimes/exercise.rs
@@ -62,6 +62,10 @@ struct Field<'a> {
     value: FieldValue<'a>,
 }
 
+trait ProtoMessage<'a>: Default + 'a {
+    fn add_field(&mut self, field: Field<'a>) -> Result<(), Error>;
+}
+
 impl TryFrom<u64> for WireType {
     type Error = Error;
 
@@ -171,56 +175,66 @@ fn parse_field(data: &[u8]) -> Result<(Field, &[u8]), Error> {
 /// Parse a message in the given data, calling `field_callback` for each field in the message.
 ///
 /// The entire input is consumed.
-fn parse_message(
-    mut data: &[u8],
-    field_callback: impl Fn(Field) -> Result<(), Error>,
-) -> Result<(), Error> {
+fn parse_message<'a, T: ProtoMessage<'a>>(mut data: &'a [u8]) -> Result<T, Error> {
+    let mut result = T::default();
     while !data.is_empty() {
         let parsed = parse_field(data)?;
-        field_callback(parsed.0)?;
+        result.add_field(parsed.0)?;
         data = parsed.1;
     }
-    Ok(())
+    Ok(result)
 }
 // ANCHOR_END: parse_message
 
+// ANCHOR: message_types
+#[derive(Debug, Default)]
+struct PhoneNumber<'a> {
+    number: &'a str,
+    type_: &'a str,
+}
+
+#[derive(Debug, Default)]
+struct Person<'a> {
+    name: &'a str,
+    id: u64,
+    phone: Vec<PhoneNumber<'a>>,
+}
+// ANCHOR_END: message_types
+
+impl<'a> ProtoMessage<'a> for Person<'a> {
+    fn add_field(&mut self, field: Field<'a>) -> Result<(), Error> {
+        match field.field_num {
+            1 => self.name = field.value.as_string()?,
+            2 => self.id = field.value.as_u64()?,
+            3 => self.phone.push(parse_message(field.value.as_bytes()?)?),
+            _ => {} // skip everything else
+        }
+        Ok(())
+    }
+}
+
+impl<'a> ProtoMessage<'a> for PhoneNumber<'a> {
+    fn add_field(&mut self, field: Field<'a>) -> Result<(), Error> {
+        match field.field_num {
+            1 => self.number = field.value.as_string()?,
+            2 => self.type_ = field.value.as_string()?,
+            _ => {} // skip everything else
+        }
+        Ok(())
+    }
+}
+
 // ANCHOR: main
 fn main() {
-    /// Handle a field in a Person message.
-    fn person_field(field: Field) -> Result<(), Error> {
-        match field.field_num {
-            1 => println!("name: {}", field.value.as_string()?),
-            2 => println!("id: {}", field.value.as_u64()?),
-            3 => {
-                println!("phone:");
-                parse_message(field.value.as_bytes()?, phone_number_field)?;
-            }
-            _ => {} // skip everything else
-        }
-        Ok(())
-    }
-
-    /// Handle a field in a PhoneNumber message.
-    fn phone_number_field(field: Field) -> Result<(), Error> {
-        match field.field_num {
-            1 => println!("  number: {}", field.value.as_string()?),
-            2 => println!("  type: {}", field.value.as_string()?),
-            _ => {} // skip everything else
-        }
-        Ok(())
-    }
-
-    parse_message(
-        &[
-            0x0a, 0x07, 0x6d, 0x61, 0x78, 0x77, 0x65, 0x6c, 0x6c, 0x10, 0x2a, 0x1a, 0x16,
-            0x0a, 0x0e, 0x2b, 0x31, 0x32, 0x30, 0x32, 0x2d, 0x35, 0x35, 0x35, 0x2d, 0x31,
-            0x32, 0x31, 0x32, 0x12, 0x04, 0x68, 0x6f, 0x6d, 0x65, 0x1a, 0x18, 0x0a, 0x0e,
-            0x2b, 0x31, 0x38, 0x30, 0x30, 0x2d, 0x38, 0x36, 0x37, 0x2d, 0x35, 0x33, 0x30,
-            0x38, 0x12, 0x06, 0x6d, 0x6f, 0x62, 0x69, 0x6c, 0x65,
-        ],
-        person_field,
-    )
-    .unwrap()
+    let person: Person = parse_message(&[
+        0x0a, 0x07, 0x6d, 0x61, 0x78, 0x77, 0x65, 0x6c, 0x6c, 0x10, 0x2a, 0x1a, 0x16,
+        0x0a, 0x0e, 0x2b, 0x31, 0x32, 0x30, 0x32, 0x2d, 0x35, 0x35, 0x35, 0x2d, 0x31,
+        0x32, 0x31, 0x32, 0x12, 0x04, 0x68, 0x6f, 0x6d, 0x65, 0x1a, 0x18, 0x0a, 0x0e,
+        0x2b, 0x31, 0x38, 0x30, 0x30, 0x2d, 0x38, 0x36, 0x37, 0x2d, 0x35, 0x33, 0x30,
+        0x38, 0x12, 0x06, 0x6d, 0x6f, 0x62, 0x69, 0x6c, 0x65,
+    ])
+    .unwrap();
+    println!("{:#?}", person);
 }
 // ANCHOR_END: main