Repository: capnproto/capnproto-rust (language: rust)

Issue:
Lossy utf-8 string support
The reference c++ library allows users to decide how to approach invalid utf8 encoded text.
for example.

```capnp
struct Message {
    stringField @0 :Text;
}
```

```cpp
...
::capnp::FlatArrayMessageReader reader = ::capnp::FlatArrayMessageReader(
    kj::ArrayPtr<capnp::word>(reinterpret_cast<capnp::word*>(buffer), sz)
);
Message::Reader root = reader.getRoot<Message>();
std::string msg = root.hasStringField() ? root.getStringField() : "";
std::string valid_utf8;
utf8::replace_invalid(msg.begin(), msg.end(), std::back_inserter(valid_utf8));
handle_valid_utf8(std::move(valid_utf8));
```

Here we have a Message with one text stringField, if stringField contains invalid utf8 data, the replace_invalid()[1] method replaces the invalid characters with the `0xfffd`, the same approach which is the standard rust String doing for String::from_utf8_lossy(). We are losing some information but still, this is a better approach than throwing out the Message entirely (depending on the use case ofc.).

I am having a hard time replicating something similar in rust with this library. From what I have tried I am only able to extract a valid UTF-8 &str or error. But I cannot find a way to replace the invalid characters with Unicode replacement characters[2]. 

```rust
let msg = capnp::serialize::read_message_from_flat_slice(
    &mut buf,
    capnp::message::ReaderOptions::new(),
)
.unwrap();
let root = msg.get_root::<http_log_capnp::message::Reader>().unwrap();
if root.has_string_field() {
    match root.get_string_field() {
        Ok(string_field) => println!("valid utf8 {:?}", string_field),
        Err(e) => println!("invalid utf8 {:?}", e),
    }
}; 
 ```
 
 Is there any way how achieve something similar to the c++ example above?

[1]: https://github.com/nemtrif/utfcpp/blob/master/source/utf8/checked.h#L132
[2]: https://www.fileformat.info/info/unicode/char/fffd/index.htm



Discussion / hints:
Yeah, currently `capnp::text::Reader` is just an alias of Rust's `&str`, so there's not a good way to get a handle on one without already doing the utf8 validation.  A terrible hack you could do is to define a second message type
```capnp
struct Message2 {
    stringField @0 :Data;
}
```
and read the message at that type.

I think the real fix will be to make `capnp::text::Reader` a proper wrapper that only optionally does the utf8 validation. That can help with performance too, because not all consumers actually need to get read the value as a`&str`.
Thank you for the reply.
I will try changing the rust consumer scheme to data, looking at the serialization the only difference between the Text and Data fields in capnp is trailing a null byte so it should be fine. Although, the proper fix you are proposing would be nice!
> I think the real fix will be to make capnp::text::Reader a proper wrapper that only optionally does the utf8 validation. That can help with performance too, because not all consumers actually need to get read the value as a&str.

That would immensely help my use case too, which involves calling `.get_text` several times. UTF validation does show up in my flamegraphs.
Hello,
We're looking into addressing this issue implementing your solution David, so make `text::Reader` a proper wrapper. The idea would be:
- To make `text::Reader` a wrapper around `data::Reader`, so only containing an array of bytes.
- To expose a method for text::Reader `to_str(self) -> Result<&str>` performing the UTF8 check.
- On the codegen side, for a given Text field `foo`, expose the following methods: 
    - `get_foo(self) -> Result<&str>` (no API break as it previously returned `Result<text::Reader>`) that would perform the UTF8 check and the conversion to string.
    - `get_foo_as_bytes(self)` that would return either the underlying `data::Reader` or directly the `&[u8]` (this is most likely equivalent though).

This should allow to have no breaking change on the user's side once the API is generated as the `get_foo` method will behave the same and still return a `Result<&str>`, it will only additionally perform the UTF8 check itself, compared to doing it at construction time like before. It will however require schema regeneration.

Thoughts? Happy to make a PR once I've finished implementing if that sounds suitable.
Sounds good to me!
@antoinecordelle could you describe more about why you need this feature? In particular, is it for performance reasons, or is it just to be able to recover the data in the event of encountering some non-utf8 bytes? If it's the latter, then there's a possibility that we could get away with a much smaller change than #429. 

I'd also be curious to hear about what @osiewicz is doing. Why does `get_text()` need to be called multiple times, what how would making utf-8 validation lazier help that?

In any case, having more context will help justify whatever decision we make.
#429 is going to require a lot of people to update their code and will make things more verbose. I want to make sure we have a solid justification for doing that.
Hey, I've moved on from the project since making that comment. @Shaddy should be able to give more perspective.
`.get_text` being called multiple times was admittedly specific to that project. Having the validation be optional would mean one could run the validation once and then use the non-validating API (with something like `from_utf8_unchecked`) if the first validation was successful.
It's not my cup of tea anymore though, so feel free to ignore.
Sure. In this case, this is mostly to be able to recover the data from Text fields that may have some non-utf8 bytes. Performance reasons can be a nice to have but the main reason was non-utf8 text. One note that differs from the initial discussions in this issue though, is that I'd need to be able to recover the data in a non-lossy way, so not replacing invalid bytes by placeholders. 

I mostly implemented it as such in #429 as that seemed like the change that conceptually made the most sense to me. But happy to hear about the simpler way of achieving that if that can limit the breaking change and not require people to change their code.

Fix patch:
diff --git a/benchmark/carsales.rs b/benchmark/carsales.rs
index 9942ad10e..0c1769aff 100644
--- a/benchmark/carsales.rs
+++ b/benchmark/carsales.rs
@@ -82,8 +82,8 @@ const MAKES: [&str; 5] = ["Toyota", "GM", "Ford", "Honda", "Tesla"];
 const MODELS: [&str; 6] = ["Camry", "Prius", "Volt", "Accord", "Leaf", "Model S"];
 
 pub fn random_car(rng: &mut FastRand, mut car: car::Builder) {
-    car.set_make(MAKES[rng.next_less_than(MAKES.len() as u32) as usize]);
-    car.set_model(MODELS[rng.next_less_than(MODELS.len() as u32) as usize]);
+    car.set_make(MAKES[rng.next_less_than(MAKES.len() as u32) as usize].into());
+    car.set_model(MODELS[rng.next_less_than(MODELS.len() as u32) as usize].into());
 
     car.set_color(
         (rng.next_less_than(Color::Silver as u32 + 1) as u16)
diff --git a/benchmark/catrank.rs b/benchmark/catrank.rs
index 0f606671b..176e99011 100644
--- a/benchmark/catrank.rs
+++ b/benchmark/catrank.rs
@@ -84,7 +84,7 @@ impl crate::TestCase for CatRank {
                 snippet.push_str(WORDS[rng.next_less_than(WORDS.len() as u32) as usize]);
             }
 
-            result.set_snippet(&snippet);
+            result.set_snippet(snippet[..].into());
         }
 
         good_count
@@ -101,7 +101,7 @@ impl crate::TestCase for CatRank {
         for i in 0..results.len() {
             let result = results.get(i);
             let mut score = result.get_score();
-            let snippet = result.get_snippet()?;
+            let snippet = result.get_snippet()?.to_str()?;
             if snippet.contains(" cat ") {
                 score *= 10000.0;
             }
diff --git a/capnp-rpc/examples/hello-world/client.rs b/capnp-rpc/examples/hello-world/client.rs
index 865d96748..af59b3d73 100644
--- a/capnp-rpc/examples/hello-world/client.rs
+++ b/capnp-rpc/examples/hello-world/client.rs
@@ -58,11 +58,14 @@ pub async fn main() -> Result<(), Box<dyn std::error::Error>> {
             tokio::task::spawn_local(rpc_system);
 
             let mut request = hello_world.say_hello_request();
-            request.get().init_request().set_name(&msg);
+            request.get().init_request().set_name(msg[..].into());
 
             let reply = request.send().promise.await?;
 
-            println!("received: {}", reply.get()?.get_reply()?.get_message()?);
+            println!(
+                "received: {}",
+                reply.get()?.get_reply()?.get_message()?.to_str()?
+            );
             Ok(())
         })
         .await
diff --git a/capnp-rpc/examples/hello-world/server.rs b/capnp-rpc/examples/hello-world/server.rs
index e88a18496..f2ea14e8f 100644
--- a/capnp-rpc/examples/hello-world/server.rs
+++ b/capnp-rpc/examples/hello-world/server.rs
@@ -36,10 +36,10 @@ impl hello_world::Server for HelloWorldImpl {
         mut results: hello_world::SayHelloResults,
     ) -> Promise<(), ::capnp::Error> {
         let request = pry!(pry!(params.get()).get_request());
-        let name = pry!(request.get_name());
+        let name = pry!(pry!(request.get_name()).to_str());
         let message = format!("Hello, {name}!");
 
-        results.get().init_reply().set_message(&message);
+        results.get().init_reply().set_message(message[..].into());
 
         Promise::ok(())
     }
diff --git a/capnp-rpc/examples/pubsub/client.rs b/capnp-rpc/examples/pubsub/client.rs
index ae78fe3cf..9ad0da313 100644
--- a/capnp-rpc/examples/pubsub/client.rs
+++ b/capnp-rpc/examples/pubsub/client.rs
@@ -35,7 +35,7 @@ impl subscriber::Server<::capnp::text::Owned> for SubscriberImpl {
     ) -> Promise<(), ::capnp::Error> {
         println!(
             "message from publisher: {}",
-            pry!(pry!(params.get()).get_message())
+            pry!(pry!(pry!(params.get()).get_message()).to_str())
         );
         Promise::ok(())
     }
diff --git a/capnp-rpc/examples/pubsub/server.rs b/capnp-rpc/examples/pubsub/server.rs
index cb3a1fe08..8563caf90 100644
--- a/capnp-rpc/examples/pubsub/server.rs
+++ b/capnp-rpc/examples/pubsub/server.rs
@@ -167,7 +167,10 @@ pub async fn main() -> Result<(), Box<dyn std::error::Error>> {
                             subscriber.requests_in_flight += 1;
                             let mut request = subscriber.client.push_message_request();
                             request.get().set_message(
-                            &format!("system time is: {:?}", ::std::time::SystemTime::now())[..])?;
+                                (&format!("system time is: {:?}", ::std::time::SystemTime::now())
+                                    [..])
+                                    .into(),
+                            )?;
                             let subscribers2 = subscribers1.clone();
                             tokio::task::spawn_local(request.send().promise.map(
                                 move |r| match r {
diff --git a/capnp-rpc/src/rpc.rs b/capnp-rpc/src/rpc.rs
index fb3363493..808e75e01 100644
--- a/capnp-rpc/src/rpc.rs
+++ b/capnp-rpc/src/rpc.rs
@@ -355,7 +355,7 @@ fn to_pipeline_ops(
 }
 
 fn from_error(error: &Error, mut builder: exception::Builder) {
-    builder.set_reason(&error.to_string());
+    builder.set_reason(error.to_string()[..].into());
     let typ = match error.kind {
         ::capnp::ErrorKind::Failed => exception::Type::Failed,
         ::capnp::ErrorKind::Overloaded => exception::Type::Overloaded,
@@ -379,10 +379,14 @@ fn remote_exception_to_error(exception: exception::Reader) -> Error {
         (Ok(exception::Type::Unimplemented), Ok(reason)) => {
             (::capnp::ErrorKind::Unimplemented, reason)
         }
-        _ => (::capnp::ErrorKind::Failed, "(malformed error)"),
+        _ => (::capnp::ErrorKind::Failed, "(malformed error)".into()),
+    };
+    let reason_str = match reason.to_str() {
+        Ok(s) => s,
+        Err(_) => "<malformed utf-8 in error reason>",
     };
     Error {
-        extra: format!("remote exception: {reason}"),
+        extra: format!("remote exception: {reason_str}"),
         kind,
     }
 }
diff --git a/capnp/src/private/layout.rs b/capnp/src/private/layout.rs
index f52147f80..076e34de9 100644
--- a/capnp/src/private/layout.rs
+++ b/capnp/src/private/layout.rs
@@ -1730,8 +1730,7 @@ mod wire_helpers {
 
         SegmentAnd {
             segment_id,
-            value: text::Builder::new(slice::from_raw_parts_mut(ptr, size as usize), 0)
-                .expect("empty text builder should be valid utf-8"),
+            value: text::Builder::new(slice::from_raw_parts_mut(ptr, size as usize)),
         }
     }
 
@@ -1740,12 +1739,16 @@ mod wire_helpers {
         arena: &'a mut dyn BuilderArena,
         reff: *mut WirePointer,
         segment_id: u32,
-        value: &str,
+        value: crate::text::Reader<'_>,
     ) -> SegmentAnd<text::Builder<'a>> {
         let value_bytes = value.as_bytes();
         // TODO make sure the string is not longer than 2 ** 29.
         let mut allocation = init_text_pointer(arena, reff, segment_id, value_bytes.len() as u32);
-        allocation.value.push_str(value);
+        allocation
+            .value
+            .reborrow()
+            .as_bytes_mut()
+            .copy_from_slice(value_bytes);
         allocation
     }
 
@@ -1758,7 +1761,7 @@ mod wire_helpers {
     ) -> Result<text::Builder<'a>> {
         let ref_target = if (*reff).is_null() {
             match default {
-                None => return text::Builder::new(&mut [], 0),
+                None => return Ok(text::Builder::new(&mut [])),
                 Some(d) => {
                     let (new_ref_target, new_reff, new_segment_id) = copy_message(
                         arena,
@@ -1793,10 +1796,10 @@ mod wire_helpers {
         }
 
         // Subtract 1 from the size for the NUL terminator.
-        text::Builder::new(
+        Ok(text::Builder::with_pos(
             slice::from_raw_parts_mut(ptr, (count - 1) as usize),
-            count - 1,
-        )
+            (count - 1) as usize,
+        ))
     }
 
     #[inline]
@@ -2615,7 +2618,7 @@ mod wire_helpers {
     ) -> Result<text::Reader<'a>> {
         if (*reff).is_null() {
             match default {
-                None => return Ok(""),
+                None => return Ok("".into()),
                 Some(d) => {
                     reff = d.as_ptr() as *const WirePointer;
                     arena = &super::NULL_ARENA;
@@ -2661,7 +2664,10 @@ mod wire_helpers {
             ));
         }
 
-        text::new_reader(slice::from_raw_parts(str_ptr, size as usize - 1))
+        Ok(text::Reader(slice::from_raw_parts(
+            str_ptr,
+            size as usize - 1,
+        )))
     }
 
     #[inline]
@@ -3282,7 +3288,7 @@ impl<'a> PointerBuilder<'a> {
         }
     }
 
-    pub fn set_text(&mut self, value: &str) {
+    pub fn set_text(&mut self, value: crate::text::Reader<'_>) {
         unsafe {
             wire_helpers::set_text_pointer(self.arena, self.pointer, self.segment_id, value);
         }
diff --git a/capnp/src/stringify.rs b/capnp/src/stringify.rs
index a7bb8a7a4..d11bfbeb7 100644
--- a/capnp/src/stringify.rs
+++ b/capnp/src/stringify.rs
@@ -46,7 +46,7 @@ impl Indent {
     }
 }
 
-fn cvt<T>(r: crate::Result<T>) -> Result<T, fmt::Error> {
+fn cvt<T, E>(r: core::result::Result<T, E>) -> Result<T, fmt::Error> {
     match r {
         Ok(v) => Ok(v),
         Err(_) => Err(fmt::Error),
@@ -72,7 +72,9 @@ pub(crate) fn print(
         dynamic_value::Reader::Float32(x) => formatter.write_fmt(format_args!("{x}")),
         dynamic_value::Reader::Float64(x) => formatter.write_fmt(format_args!("{x}")),
         dynamic_value::Reader::Enum(e) => match cvt(e.get_enumerant())? {
-            Some(enumerant) => formatter.write_str(cvt(enumerant.get_proto().get_name())?),
+            Some(enumerant) => {
+                formatter.write_str(cvt(cvt(enumerant.get_proto().get_name())?.to_str())?)
+            }
             None => formatter.write_fmt(format_args!("{}", e.get_value())),
         },
         dynamic_value::Reader::Text(t) => formatter.write_fmt(format_args!("{t:?}")),
@@ -131,7 +133,7 @@ pub(crate) fn print(
                             indent2.comma(formatter)?;
                         }
                         indent2.maybe_newline(formatter)?;
-                        formatter.write_str(cvt(ff.get_proto().get_name())?)?;
+                        formatter.write_str(cvt(cvt(ff.get_proto().get_name())?.to_str())?)?;
                         formatter.write_str(" = ")?;
                         print(cvt(st.get(ff))?, formatter, indent2)?;
                         union_field = None;
@@ -144,7 +146,7 @@ pub(crate) fn print(
                         indent2.comma(formatter)?;
                     }
                     indent2.maybe_newline(formatter)?;
-                    formatter.write_str(cvt(field.get_proto().get_name())?)?;
+                    formatter.write_str(cvt(cvt(field.get_proto().get_name())?.to_str())?)?;
                     formatter.write_str(" = ")?;
                     print(cvt(st.get(field))?, formatter, indent2)?;
                 }
@@ -155,7 +157,7 @@ pub(crate) fn print(
                     indent2.comma(formatter)?;
                 }
                 indent2.maybe_newline(formatter)?;
-                formatter.write_str(cvt(ff.get_proto().get_name())?)?;
+                formatter.write_str(cvt(cvt(ff.get_proto().get_name())?.to_str())?)?;
                 formatter.write_str(" = ")?;
                 print(cvt(st.get(ff))?, formatter, indent2)?;
             }
diff --git a/capnp/src/text.rs b/capnp/src/text.rs
index 50344d7b9..e730bb804 100644
--- a/capnp/src/text.rs
+++ b/capnp/src/text.rs
@@ -21,9 +21,9 @@
 
 //! UTF-8 encoded text.
 
-use core::{convert, ops, str};
+use core::str;
 
-use crate::{Error, ErrorKind, Result};
+use crate::Result;
 
 #[derive(Copy, Clone)]
 pub struct Owned(());
@@ -39,12 +39,56 @@ impl crate::introspect::Introspect for Owned {
     }
 }
 
-pub type Reader<'a> = &'a str;
+/// Wrapper around utf-8 encoded text.
+/// This is defined as a tuple struct to allow pattern matching
+/// on it via byte literals (for example `text::Reader(b"hello")`).
+#[derive(Copy, Clone, PartialEq)]
+pub struct Reader<'a>(pub &'a [u8]);
 
-pub fn new_reader(v: &[u8]) -> Result<Reader<'_>> {
-    match str::from_utf8(v) {
-        Ok(v) => Ok(v),
-        Err(e) => Err(Error::from_kind(ErrorKind::TextContainsNonUtf8Data(e))),
+impl<'a> core::cmp::PartialEq<&'a str> for Reader<'a> {
+    fn eq(&self, other: &&'a str) -> bool {
+        self.as_bytes() == other.as_bytes()
+    }
+}
+
+impl<'a> core::cmp::PartialEq<Reader<'a>> for &'a str {
+    fn eq(&self, other: &Reader<'a>) -> bool {
+        self.as_bytes() == other.as_bytes()
+    }
+}
+
+impl<'a> core::fmt::Debug for Reader<'a> {
+    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
+        match self.to_str() {
+            Ok(s) => write!(f, "{:?}", s),
+            Err(_) => write!(f, "<invalid utf-8: {:?}>", self.as_bytes()),
+        }
+    }
+}
+
+impl<'a> From<&'a str> for Reader<'a> {
+    fn from(value: &'a str) -> Self {
+        Self(value.as_bytes())
+    }
+}
+
+impl<'a> From<&'a [u8]> for Reader<'a> {
+    fn from(value: &'a [u8]) -> Self {
+        Self(value)
+    }
+}
+
+impl<'a, const N: usize> From<&'a [u8; N]> for Reader<'a> {
+    fn from(value: &'a [u8; N]) -> Self {
+        Self(&value[..])
+    }
+}
+
+impl<'a> TryFrom<Reader<'a>> for &'a str {
+    type Error = core::str::Utf8Error;
+    fn try_from(value: Reader<'a>) -> core::result::Result<&'a str, core::str::Utf8Error> {
+        let Reader(v) = value;
+        str::from_utf8(v)
     }
 }
 
@@ -57,36 +101,102 @@ impl<'a> crate::traits::FromPointerReader<'a> for Reader<'a> {
     }
 }
 
+impl<'a> Reader<'a> {
+    pub fn len(&self) -> usize {
+        self.as_bytes().len()
+    }
+
+    pub fn as_bytes(self) -> &'a [u8] {
+        let Self(d) = self;
+        d
+    }
+
+    /// Converts to a `str`, returning a error if the data contains invalid utf-8.
+    pub fn to_str(self) -> core::result::Result<&'a str, core::str::Utf8Error> {
+        let Self(s) = self;
+        str::from_utf8(s)
+    }
+
+    #[cfg(feature = "alloc")]
+    /// Converts to a `String`, returning a error if the data contains invalid utf-8.
+    pub fn to_string(self) -> core::result::Result<alloc::string::String, core::str::Utf8Error> {
+        Ok(self.to_str()?.into())
+    }
+}
+
 pub struct Builder<'a> {
+    /// Does not include the trailing null byte.
     bytes: &'a mut [u8],
+
+    /// Position at which `push_ascii()` and `push_str()` will write to.
     pos: usize,
 }
 
+impl<'a> core::cmp::PartialEq for Builder<'a> {
+    fn eq(&self, other: &Self) -> bool {
+        self.bytes == other.bytes
+    }
+}
+
+impl<'a> core::cmp::PartialEq<&'a str> for Builder<'a> {
+    fn eq(&self, other: &&'a str) -> bool {
+        self.bytes == other.as_bytes()
+    }
+}
+
+impl<'a> core::cmp::PartialEq<Builder<'a>> for &'a str {
+    fn eq(&self, other: &Builder<'a>) -> bool {
+        self.as_bytes() == other.bytes
+    }
+}
+
 impl<'a> Builder<'a> {
-    pub fn new(bytes: &mut [u8], pos: u32) -> Result<Builder<'_>> {
-        if pos != 0 {
-            if let Err(e) = str::from_utf8(bytes) {
-                return Err(Error::from_kind(ErrorKind::TextContainsNonUtf8Data(e)));
-            }
-        }
-        Ok(Builder {
-            bytes,
-            pos: pos as usize,
-        })
+    pub fn new(bytes: &mut [u8]) -> Builder<'_> {
+        Builder { bytes, pos: 0 }
+    }
+
+    pub fn with_pos(bytes: &mut [u8], pos: usize) -> Builder<'_> {
+        Builder { bytes, pos }
+    }
+
+    pub fn len(&self) -> usize {
+        self.bytes.len()
+    }
+
+    pub fn as_bytes(self) -> &'a [u8] {
+        &self.bytes[..]
+    }
+
+    /// Converts to a `str`, returning a error if the data contains invalid utf-8.
+    pub fn to_str(self) -> core::result::Result<&'a str, core::str::Utf8Error> {
+        str::from_utf8(&self.bytes[..])
+    }
+
+    #[cfg(feature = "alloc")]
+    /// Converts to a `String`, returning a error if the data contains invalid utf-8.
+    pub fn to_string(self) -> core::result::Result<alloc::string::String, core::str::Utf8Error> {
+        Ok(self.to_str()?.into())
+    }
+
+    pub fn as_bytes_mut(self) -> &'a mut [u8] {
+        &mut self.bytes[..]
     }
 
+    /// Writes a single ascii character at position `pos` and increments `pos`.
     pub fn push_ascii(&mut self, ascii: u8) {
         assert!(ascii < 128);
         self.bytes[self.pos] = ascii;
         self.pos += 1;
     }
 
+    /// Writes a string at position `pos` and increases `pos` a corresponding amount.
     pub fn push_str(&mut self, string: &str) {
         let bytes = string.as_bytes();
         self.bytes[self.pos..(self.pos + bytes.len())].copy_from_slice(bytes);
         self.pos += bytes.len();
     }
 
+    /// Zeroes all data and resets `pos`.
     pub fn clear(&mut self) {
         for b in &mut self.bytes[..self.pos] {
             *b = 0;
@@ -102,29 +212,20 @@ impl<'a> Builder<'a> {
     }
 
     pub fn into_reader(self) -> Reader<'a> {
-        str::from_utf8(self.bytes).unwrap()
+        Reader(self.bytes)
     }
-}
-
-impl<'a> ops::Deref for Builder<'a> {
-    type Target = str;
-    fn deref(&self) -> &str {
-        str::from_utf8(self.bytes)
-            .expect("text::Builder contents are checked for utf8-validity upon construction")
-    }
-}
 
-impl<'a> ops::DerefMut for Builder<'a> {
-    fn deref_mut(&mut self) -> &mut str {
-        str::from_utf8_mut(self.bytes)
-            .expect("text::Builder contents are checked for utf8-validity upon construction")
+    pub fn reborrow_as_reader(&self) -> Reader<'_> {
+        Reader(&self.bytes[..])
     }
 }
 
-impl<'a> convert::AsRef<str> for Builder<'a> {
-    fn as_ref(&self) -> &str {
-        str::from_utf8(self.bytes)
-            .expect("text::Builder contents are checked for utf8-validity upon construction")
+impl<'a> core::fmt::Debug for Builder<'a> {
+    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
+        match self.reborrow_as_reader().to_str() {
+            Ok(s) => write!(f, "{:?}", s),
+            Err(_) => write!(f, "<invalid utf-8>"),
+        }
     }
 }
 
@@ -151,12 +252,31 @@ impl<'a> crate::traits::SetPointerBuilder for Reader<'a> {
     }
 }
 
+// Extra impl to make any_pointer::Builder::set_as() and similar methods work
+// more smoothly.
+impl<'a> crate::traits::SetPointerBuilder for &'a str {
+    fn set_pointer_builder<'b>(
+        mut pointer: crate::private::layout::PointerBuilder<'b>,
+        value: &'a str,
+        _canonicalize: bool,
+    ) -> Result<()> {
+        pointer.set_text(value.into());
+        Ok(())
+    }
+}
+
 impl<'a> From<Reader<'a>> for crate::dynamic_value::Reader<'a> {
     fn from(t: Reader<'a>) -> crate::dynamic_value::Reader<'a> {
         crate::dynamic_value::Reader::Text(t)
     }
 }
 
+impl<'a> From<&'a str> for crate::dynamic_value::Reader<'a> {
+    fn from(t: &'a str) -> crate::dynamic_value::Reader<'a> {
+        crate::dynamic_value::Reader::Text(t.into())
+    }
+}
+
 impl<'a> From<Builder<'a>> for crate::dynamic_value::Builder<'a> {
     fn from(t: Builder<'a>) -> crate::dynamic_value::Builder<'a> {
         crate::dynamic_value::Builder::Text(t)
diff --git a/capnpc/src/codegen.rs b/capnpc/src/codegen.rs
index 7cd7ef89c..9449718eb 100644
--- a/capnpc/src/codegen.rs
+++ b/capnpc/src/codegen.rs
@@ -123,7 +123,7 @@ impl CodeGenerationCommand {
         for requested_file in ctx.request.get_requested_files()? {
             let id = requested_file.get_id();
             let mut filepath = self.output_directory.to_path_buf();
-            let requested = ::std::path::PathBuf::from(requested_file.get_filename()?);
+            let requested = ::std::path::PathBuf::from(requested_file.get_filename()?.to_str()?);
             filepath.push(requested);
             if let Some(parent) = filepath.parent() {
                 ::std::fs::create_dir_all(parent).map_err(convert_io_err)?;
@@ -132,13 +132,14 @@ impl CodeGenerationCommand {
             let root_name = path_to_stem_string(&filepath)?.replace('-', "_");
             filepath.set_file_name(&format!("{root_name}_capnp.rs"));
 
-            let lines = Branch(vec![
+            let lines =
+                Branch(vec![
                 Line(
                     "// @generated by the capnpc-rust plugin to the Cap'n Proto schema compiler."
                         .to_string(),
                 ),
                 line("// DO NOT EDIT."),
-                Line(format!("// source: {}", requested_file.get_filename()?)),
+                Line(format!("// source: {}", requested_file.get_filename()?.to_str()?)),
                 BlankLine,
                 generate_node(&ctx, id, &root_name)?,
             ]);
@@ -247,7 +248,7 @@ impl<'a> GeneratorContext<'a> {
             let id = requested_file.get_id();
 
             for import in requested_file.get_imports()? {
-                let importpath = ::std::path::Path::new(import.get_name()?);
+                let importpath = ::std::path::Path::new(import.get_name()?.to_str()?);
                 let root_name: String = format!(
                     "{}_capnp",
                     path_to_stem_string(importpath)?.replace('-', "_")
@@ -267,7 +268,7 @@ impl<'a> GeneratorContext<'a> {
                 )?;
             }
 
-            let root_name = path_to_stem_string(requested_file.get_filename()?)?;
+            let root_name = path_to_stem_string(requested_file.get_filename()?.to_str()?)?;
             let root_mod = format!("{}_capnp", root_name.replace('-', "_"));
             ctx.populate_scope_map(
                 default_parent_module_scope.clone(),
@@ -329,7 +330,7 @@ impl<'a> GeneratorContext<'a> {
                     Ok(schema_capnp::node::Enum(_enum_reader)) => {
                         self.populate_scope_map(
                             scope_names.clone(),
-                            nested_node.get_name()?.to_string(),
+                            nested_node.get_name()?.to_string()?,
                             NameKind::Verbatim,
                             nested_node_id,
                         )?;
@@ -337,7 +338,7 @@ impl<'a> GeneratorContext<'a> {
                     _ => {
                         self.populate_scope_map(
                             scope_names.clone(),
-                            nested_node.get_name()?.to_string(),
+                            nested_node.get_name()?.to_string()?,
                             NameKind::Module,
                             nested_node_id,
                         )?;
@@ -530,7 +531,7 @@ const OPTION_ANNOTATION_ID: u64 = 0xabfef22c4ee1964e;
 
 fn name_annotation_value(annotation: schema_capnp::annotation::Reader) -> capnp::Result<&str> {
     if let schema_capnp::value::Text(t) = annotation.get_value()?.which()? {
-        let name = t?;
+        let name = t?.to_str()?;
         for c in name.chars() {
             if !(c == '_' || c.is_alphanumeric()) {
                 return Err(capnp::Error::failed(
@@ -553,7 +554,7 @@ fn get_field_name(field: schema_capnp::field::Reader) -> capnp::Result<&str> {
             return name_annotation_value(annotation);
         }
     }
-    field.get_name()
+    Ok(field.get_name()?.to_str()?)
 }
 
 fn get_enumerant_name(enumerant: schema_capnp::enumerant::Reader) -> capnp::Result<&str> {
@@ -562,12 +563,12 @@ fn get_enumerant_name(enumerant: schema_capnp::enumerant::Reader) -> capnp::Resu
             return name_annotation_value(annotation);
         }
     }
-    enumerant.get_name()
+    Ok(enumerant.get_name()?.to_str()?)
 }
 
 fn get_parent_module(annotation: schema_capnp::annotation::Reader) -> capnp::Result<Vec<String>> {
     if let schema_capnp::value::Text(t) = annotation.get_value()?.which()? {
-        let module = t?;
+        let module = t?.to_str()?;
         Ok(module.split("::").map(|x| x.to_string()).collect())
     } else {
         Err(capnp::Error::failed(
@@ -662,7 +663,7 @@ fn get_params(ctx: &GeneratorContext, mut node_id: u64) -> ::capnp::Result<Vec<S
         let parameters = node.get_parameters()?;
 
         for parameter in parameters.into_iter().rev() {
-            result.push(parameter.get_name()?.into());
+            result.push(parameter.get_name()?.to_str()?.into());
         }
 
         node_id = node.get_scope_id();
@@ -1301,7 +1302,7 @@ fn used_params_of_type(
                 let the_struct = &ctx.node_map[&def.get_scope_id()];
                 let parameters = the_struct.get_parameters()?;
                 let parameter = parameters.get(u32::from(def.get_parameter_index()));
-                let parameter_name = parameter.get_name()?;
+                let parameter_name = parameter.get_name()?.to_str()?;
                 used_params.insert(parameter_name.to_string());
             }
         }
@@ -1334,7 +1335,7 @@ fn used_params_of_brand(
             Some(scope) => match scope.which()? {
                 brand::scope::Inherit(()) => {
                     for param in params {
-                        used_params.insert(param.get_name()?.to_string());
+                        used_params.insert(param.get_name()?.to_string()?);
                     }
                 }
                 brand::scope::Bind(bindings_list_opt) => {
@@ -1856,7 +1857,7 @@ fn get_ty_params_of_brand(
     for (scope_id, parameter_index) in acc.into_iter() {
         let node = ctx.node_map[&scope_id];
         let p = node.get_parameters()?.get(u32::from(parameter_index));
-        result.push_str(p.get_name()?);
+        result.push_str(p.get_name()?.to_str()?);
         result.push(',');
     }
 
@@ -2496,7 +2497,7 @@ fn generate_node(
 
             let methods = interface.get_methods()?;
             for (ordinal, method) in methods.into_iter().enumerate() {
-                let name = method.get_name()?;
+                let name = method.get_name()?.to_str()?;
 
                 let param_id = method.get_param_struct_type();
                 let param_node = &ctx.node_map[&param_id];
@@ -2928,9 +2929,10 @@ fn generate_node(
                     }
                 }
 
-                (type_::Text(()), value::Text(t)) => {
-                    Line(format!("pub const {styled_name}: &str = {:?};", t?))
-                }
+                (type_::Text(()), value::Text(t)) => Line(format!(
+                    "pub const {styled_name}: &str = {:?};",
+                    t?.to_str()?
+                )),
                 (type_::Data(()), value::Data(d)) => {
                     Line(format!("pub const {styled_name}: &[u8] = &{:?};", d?))
                 }
diff --git a/capnpc/src/codegen_types.rs b/capnpc/src/codegen_types.rs
index a8790fa4e..54a201e7a 100644
--- a/capnpc/src/codegen_types.rs
+++ b/capnpc/src/codegen_types.rs
@@ -259,7 +259,7 @@ impl<'a> RustTypeInfo for type_::Reader<'a> {
                     let the_struct = &ctx.node_map[&def.get_scope_id()];
                     let parameters = the_struct.get_parameters()?;
                     let parameter = parameters.get(u32::from(def.get_parameter_index()));
-                    let parameter_name = parameter.get_name()?;
+                    let parameter_name = parameter.get_name()?.to_str()?;
                     match module {
                         Leaf::Owned => Ok(parameter_name.to_string()),
                         Leaf::Reader(lifetime) => Ok(fmt!(
@@ -376,7 +376,7 @@ pub fn do_branding(
             Some(scope) => match scope.which()? {
                 brand::scope::Inherit(()) => {
                     for param in params {
-                        arguments.push(param.get_name()?.to_string());
+                        arguments.push(param.get_name()?.to_string()?);
                     }
                 }
                 brand::scope::Bind(bindings_list_opt) => {
@@ -437,7 +437,7 @@ pub fn get_type_parameters(ctx: &GeneratorContext, node_id: u64) -> Vec<String>
         let current_node = ctx.node_map[&current_node_id];
         let mut params = Vec::new();
         for param in current_node.get_parameters().unwrap() {
-            params.push(param.get_name().unwrap().to_string());
+            params.push(param.get_name().unwrap().to_string().unwrap());
         }
 
         accumulator.push(params);
diff --git a/example/addressbook/addressbook.rs b/example/addressbook/addressbook.rs
index b5ba1f5cc..0b5eb29b6 100644
--- a/example/addressbook/addressbook.rs
+++ b/example/addressbook/addressbook.rs
@@ -37,32 +37,32 @@ pub mod addressbook {
             {
                 let mut alice = people.reborrow().get(0);
                 alice.set_id(123);
-                alice.set_name("Alice");
-                alice.set_email("alice@example.com");
+                alice.set_name("Alice".into());
+                alice.set_email("alice@example.com".into());
                 {
                     let mut alice_phones = alice.reborrow().init_phones(1);
-                    alice_phones.reborrow().get(0).set_number("555-1212");
+                    alice_phones.reborrow().get(0).set_number("555-1212".into());
                     alice_phones
                         .reborrow()
                         .get(0)
                         .set_type(person::phone_number::Type::Mobile);
                 }
-                alice.get_employment().set_school("MIT");
+                alice.get_employment().set_school("MIT".into());
             }
 
             {
                 let mut bob = people.get(1);
                 bob.set_id(456);
-                bob.set_name("Bob");
-                bob.set_email("bob@example.com");
+                bob.set_name("Bob".into());
+                bob.set_email("bob@example.com".into());
                 {
                     let mut bob_phones = bob.reborrow().init_phones(2);
-                    bob_phones.reborrow().get(0).set_number("555-4567");
+                    bob_phones.reborrow().get(0).set_number("555-4567".into());
                     bob_phones
                         .reborrow()
                         .get(0)
                         .set_type(person::phone_number::Type::Home);
-                    bob_phones.reborrow().get(1).set_number("555-7654");
+                    bob_phones.reborrow().get(1).set_number("555-7654".into());
                     bob_phones
                         .reborrow()
                         .get(1)
@@ -84,7 +84,11 @@ pub mod addressbook {
         let address_book = message_reader.get_root::<address_book::Reader>()?;
 
         for person in address_book.get_people()? {
-            println!("{}: {}", person.get_name()?, person.get_email()?);
+            println!(
+                "{}: {}",
+                person.get_name()?.to_str()?,
+                person.get_email()?.to_str()?
+            );
             for phone in person.get_phones()? {
                 let type_name = match phone.get_type() {
                     Ok(person::phone_number::Type::Mobile) => "mobile",
@@ -92,17 +96,17 @@ pub mod addressbook {
                     Ok(person::phone_number::Type::Work) => "work",
                     Err(::capnp::NotInSchema(_)) => "UNKNOWN",
                 };
-                println!("  {} phone: {}", type_name, phone.get_number()?);
+                println!("  {} phone: {}", type_name, phone.get_number()?.to_str()?);
             }
             match person.get_employment().which() {
                 Ok(person::employment::Unemployed(())) => {
                     println!("  unemployed");
                 }
                 Ok(person::employment::Employer(employer)) => {
-                    println!("  employer: {}", employer?);
+                    println!("  employer: {}", employer?.to_str()?);
                 }
                 Ok(person::employment::School(school)) => {
-                    println!("  student at: {}", school?);
+                    println!("  student at: {}", school?.to_str()?);
                 }
                 Ok(person::employment::SelfEmployed(())) => {
                     println!("  self-employed");
diff --git a/example/addressbook_send/addressbook_send.rs b/example/addressbook_send/addressbook_send.rs
index 65fdc718a..708ea8c35 100644
--- a/example/addressbook_send/addressbook_send.rs
+++ b/example/addressbook_send/addressbook_send.rs
@@ -41,32 +41,32 @@ pub mod addressbook {
             {
                 let mut alice = people.reborrow().get(0);
                 alice.set_id(123);
-                alice.set_name("Alice");
-                alice.set_email("alice@example.com");
+                alice.set_name("Alice".into());
+                alice.set_email("alice@example.com".into());
                 {
                     let mut alice_phones = alice.reborrow().init_phones(1);
-                    alice_phones.reborrow().get(0).set_number("555-1212");
+                    alice_phones.reborrow().get(0).set_number("555-1212".into());
                     alice_phones
                         .reborrow()
                         .get(0)
                         .set_type(person::phone_number::Type::Mobile);
                 }
-                alice.get_employment().set_school("MIT");
+                alice.get_employment().set_school("MIT".into());
             }
 
             {
                 let mut bob = people.get(1);
                 bob.set_id(456);
-                bob.set_name("Bob");
-                bob.set_email("bob@example.com");
+                bob.set_name("Bob".into());
+                bob.set_email("bob@example.com".into());
                 {
                     let mut bob_phones = bob.reborrow().init_phones(2);
-                    bob_phones.reborrow().get(0).set_number("555-4567");
+                    bob_phones.reborrow().get(0).set_number("555-4567".into());
                     bob_phones
                         .reborrow()
                         .get(0)
                         .set_type(person::phone_number::Type::Home);
-                    bob_phones.reborrow().get(1).set_number("555-7654");
+                    bob_phones.reborrow().get(1).set_number("555-7654".into());
                     bob_phones
                         .reborrow()
                         .get(1)


Test patch:
diff --git a/capnp-futures/test/test.rs b/capnp-futures/test/test.rs
index 557069999..45a8b5c35 100644
--- a/capnp-futures/test/test.rs
+++ b/capnp-futures/test/test.rs
@@ -35,32 +35,32 @@ mod tests {
         {
             let mut alice = people.reborrow().get(0);
             alice.set_id(123);
-            alice.set_name("Alice");
-            alice.set_email("alice@example.com");
+            alice.set_name("Alice".into());
+            alice.set_email("alice@example.com".into());
             {
                 let mut alice_phones = alice.reborrow().init_phones(1);
-                alice_phones.reborrow().get(0).set_number("555-1212");
+                alice_phones.reborrow().get(0).set_number("555-1212".into());
                 alice_phones
                     .reborrow()
                     .get(0)
                     .set_type(person::phone_number::Type::Mobile);
             }
-            alice.get_employment().set_school("MIT");
+            alice.get_employment().set_school("MIT".into());
         }
 
         {
             let mut bob = people.get(1);
             bob.set_id(456);
-            bob.set_name("Bob");
-            bob.set_email("bob@example.com");
+            bob.set_name("Bob".into());
+            bob.set_email("bob@example.com".into());
             {
                 let mut bob_phones = bob.reborrow().init_phones(2);
-                bob_phones.reborrow().get(0).set_number("555-4567");
+                bob_phones.reborrow().get(0).set_number("555-4567".into());
                 bob_phones
                     .reborrow()
                     .get(0)
                     .set_type(person::phone_number::Type::Home);
-                bob_phones.reborrow().get(1).set_number("555-7654");
+                bob_phones.reborrow().get(1).set_number("555-7654".into());
                 bob_phones
                     .reborrow()
                     .get(1)
diff --git a/capnp-rpc/test/impls.rs b/capnp-rpc/test/impls.rs
index d79f81260..71571bb50 100644
--- a/capnp-rpc/test/impls.rs
+++ b/capnp-rpc/test/impls.rs
@@ -148,7 +148,7 @@ impl test_interface::Server for TestInterface {
         }
         {
             let mut results = results.get();
-            results.set_x("foo");
+            results.set_x("foo".into());
         }
         Promise::ok(())
     }
@@ -190,7 +190,7 @@ impl test_interface::Server for TestExtends {
         }
         {
             let mut results = results.get();
-            results.set_x("bar");
+            results.set_x("bar".into());
         }
         Promise::ok(())
     }
@@ -259,7 +259,7 @@ impl test_pipeline::Server for TestPipeline {
                 return Err(Error::failed("expected x to equal 'foo'".to_string()));
             }
 
-            results.get().set_s("bar");
+            results.get().set_s("bar".into());
 
             // TODO implement better casting
             results
@@ -352,7 +352,7 @@ impl test_more_stuff::Server for TestMoreStuff {
             if response?.get()?.get_x()? != "foo" {
                 return Err(Error::failed("expected x to equal 'foo'".to_string()));
             }
-            results.get().set_s("bar");
+            results.get().set_s("bar".into());
             Ok(())
         }))
     }
@@ -372,7 +372,7 @@ impl test_more_stuff::Server for TestMoreStuff {
                 if response?.get()?.get_x()? != "foo" {
                     return Err(Error::failed("expected x to equal 'foo'".to_string()));
                 }
-                results.get().set_s("bar");
+                results.get().set_s("bar".into());
                 Ok(())
             })
         }))
@@ -438,7 +438,7 @@ impl test_more_stuff::Server for TestMoreStuff {
                     if response?.get()?.get_x()? != "foo" {
                         Err(Error::failed("expected X to equal 'foo'".to_string()))
                     } else {
-                        results.get().set_s("bar");
+                        results.get().set_s("bar".into());
                         Ok(())
                     }
                 }))
diff --git a/capnp-rpc/test/reconnect_test.rs b/capnp-rpc/test/reconnect_test.rs
index c327b7a2b..f29a203e5 100644
--- a/capnp-rpc/test/reconnect_test.rs
+++ b/capnp-rpc/test/reconnect_test.rs
@@ -76,7 +76,7 @@ impl test_interface::Server for TestInterfaceImpl {
         );
         {
             let mut results = results.get();
-            results.set_x(&s);
+            results.set_x(s[..].into());
         }
         if let Some(fut) = self.inner.borrow().block.as_ref() {
             Promise::from_future(fut.clone())
@@ -91,7 +91,7 @@ where
     F: Future<Output = capnp::Result<Response<test_interface::foo_results::Owned>>>,
 {
     match pool.run_until(fut) {
-        Ok(resp) => Ok(resp.get()?.get_x()?.to_string()),
+        Ok(resp) => Ok(resp.get()?.get_x()?.to_string()?),
         Err(err) => Err(err),
     }
 }
diff --git a/capnp-rpc/test/test_util.rs b/capnp-rpc/test/test_util.rs
index eaeb3f883..f06a54717 100644
--- a/capnp-rpc/test/test_util.rs
+++ b/capnp-rpc/test/test_util.rs
@@ -34,7 +34,7 @@ pub fn init_test_message(mut builder: test_all_types::Builder) {
     builder.set_u_int64_field(12345678901234567890);
     builder.set_float32_field(1234.5);
     builder.set_float64_field(-123e45);
-    builder.set_text_field("foo");
+    builder.set_text_field("foo".into());
     builder.set_data_field(b"bar");
     {
         let mut sub_builder = builder.reborrow().init_struct_field();
@@ -50,14 +50,14 @@ pub fn init_test_message(mut builder: test_all_types::Builder) {
         sub_builder.set_u_int64_field(345678901234567890);
         sub_builder.set_float32_field(-1.25e-10);
         sub_builder.set_float64_field(345f64);
-        sub_builder.set_text_field("baz");
+        sub_builder.set_text_field("baz".into());
         sub_builder.set_data_field(b"qux");
         {
             let mut sub_sub_builder = sub_builder.reborrow().init_struct_field();
-            sub_sub_builder.set_text_field("nested");
+            sub_sub_builder.set_text_field("nested".into());
             sub_sub_builder
                 .init_struct_field()
-                .set_text_field("really nested");
+                .set_text_field("really nested".into());
         }
         sub_builder.set_enum_field(TestEnum::Baz);
 
@@ -105,15 +105,15 @@ pub fn init_test_message(mut builder: test_all_types::Builder) {
             struct_list
                 .reborrow()
                 .get(0)
-                .set_text_field("x structlist 1");
+                .set_text_field("x structlist 1".into());
             struct_list
                 .reborrow()
                 .get(1)
-                .set_text_field("x structlist 2");
+                .set_text_field("x structlist 2".into());
             struct_list
                 .reborrow()
                 .get(2)
-                .set_text_field("x structlist 3");
+                .set_text_field("x structlist 3".into());
         }
 
         let mut enum_list = sub_builder.reborrow().init_enum_list(3);
@@ -149,7 +149,7 @@ check_test_message_impl(($typ:ident) => (
             assert_eq!(12345678901234567890, reader.reborrow().get_u_int64_field());
             assert_eq!(1234.5, reader.reborrow().get_float32_field());
             assert_eq!(-123e45, reader.reborrow().get_float64_field());
-            assert_eq!("foo", &*reader.reborrow().get_text_field().unwrap());
+            assert_eq!("foo", reader.reborrow().get_text_field().unwrap());
             assert_eq!(b"bar", &*reader.reborrow().get_data_field().unwrap());
             {
                 let mut sub_reader = reader.get_struct_field().unwrap();
@@ -165,12 +165,12 @@ check_test_message_impl(($typ:ident) => (
                 assert_eq!(345678901234567890, sub_reader.reborrow().get_u_int64_field());
                 assert_eq!(-1.25e-10, sub_reader.reborrow().get_float32_field());
                 assert_eq!(345f64, sub_reader.reborrow().get_float64_field());
-                assert_eq!("baz", &*sub_reader.reborrow().get_text_field().unwrap());
+                assert_eq!("baz", sub_reader.reborrow().get_text_field().unwrap());
                 assert_eq!(b"qux", &*sub_reader.reborrow().get_data_field().unwrap());
                 {
                     let mut sub_sub_reader = sub_reader.reborrow().get_struct_field().unwrap();
-                    assert_eq!("nested", &*sub_sub_reader.reborrow().get_text_field().unwrap());
-                    assert_eq!("really nested", &*sub_sub_reader.get_struct_field().unwrap()
+                    assert_eq!("nested", sub_sub_reader.reborrow().get_text_field().unwrap());
+                    assert_eq!("really nested", sub_sub_reader.get_struct_field().unwrap()
                                                                 .get_text_field().unwrap());
                 }
                 assert!(Ok(TestEnum::Baz) == sub_reader.reborrow().get_enum_field());
@@ -218,9 +218,9 @@ check_test_message_impl(($typ:ident) => (
                 {
                     let mut struct_list = sub_reader.reborrow().get_struct_list().unwrap();
                     assert_eq!(3, struct_list.len());
-                    assert_eq!("x structlist 1", &*struct_list.reborrow().get(0).get_text_field().unwrap());
-                    assert_eq!("x structlist 2", &*struct_list.reborrow().get(1).get_text_field().unwrap());
-                    assert_eq!("x structlist 3", &*struct_list.reborrow().get(2).get_text_field().unwrap());
+                    assert_eq!("x structlist 1", struct_list.reborrow().get(0).get_text_field().unwrap());
+                    assert_eq!("x structlist 2", struct_list.reborrow().get(1).get_text_field().unwrap());
+                    assert_eq!("x structlist 3", struct_list.reborrow().get(2).get_text_field().unwrap());
                 }
 
                 {
diff --git a/capnpc/test/dynamic.rs b/capnpc/test/dynamic.rs
index 4bd4fcca6..815cc9c74 100644
--- a/capnpc/test/dynamic.rs
+++ b/capnpc/test/dynamic.rs
@@ -57,7 +57,7 @@ fn test_unions() {
     let mut message = message::Builder::new_default();
     let mut root: test_union::Builder<'_> = message.init_root();
     root.reborrow().get_union0().set_u0f1s32(1234567);
-    root.reborrow().get_union1().set_u1f1sp("foo");
+    root.reborrow().get_union1().set_u1f1sp("foo".into());
     root.reborrow().get_union2().set_u2f0s1(true);
     root.reborrow()
         .get_union3()
@@ -119,11 +119,11 @@ fn test_unions() {
         let w = u.reborrow().which().unwrap().unwrap();
         assert_eq!("u1f1sp", w.get_proto().get_name().unwrap());
         assert_eq!(
-            "foo",
             u.get(w)
                 .unwrap()
                 .into_reader()
-                .downcast::<capnp::text::Reader<'_>>()
+                .downcast::<capnp::text::Reader<'_>>(),
+            "foo"
         );
     }
     {
@@ -236,7 +236,7 @@ fn test_stringify() {
     let mut root: test_all_types::Builder<'_> = message.init_root();
     root.set_int8_field(3);
     root.set_enum_field(TestEnum::Bar);
-    root.set_text_field("hello world");
+    root.set_text_field("hello world".into());
     root.set_data_field(&[1, 2, 3, 4, 5, 127, 255]);
     let mut bool_list = root.reborrow().init_bool_list(2);
     bool_list.set(0, false);
diff --git a/capnpc/test/test.rs b/capnpc/test/test.rs
index 6f88f3675..a4645f58c 100644
--- a/capnpc/test/test.rs
+++ b/capnpc/test/test.rs
@@ -245,7 +245,7 @@ mod tests {
         let mut test_blob = message.init_root::<test_blob::Builder<'_>>();
 
         assert_eq!(test_blob.has_text_field(), false);
-        test_blob.set_text_field("abcdefghi");
+        test_blob.set_text_field("abcdefghi".into());
         assert_eq!(test_blob.has_text_field(), true);
 
         assert_eq!(test_blob.has_data_field(), false);
@@ -264,7 +264,7 @@ mod tests {
 
         {
             let mut text = test_blob.reborrow().init_text_field(10);
-            assert_eq!(&*text, "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00");
+            assert_eq!(text, "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00");
             text.push_str("aabbccddee");
         }
 
@@ -357,8 +357,8 @@ mod tests {
 
             {
                 let mut text_list = test_complex_list.reborrow().init_text_list(2);
-                text_list.set(0, "garply");
-                text_list.set(1, "foo");
+                text_list.set(0, "garply".into());
+                text_list.set(1, "foo".into());
             }
 
             {
@@ -413,7 +413,7 @@ mod tests {
 
             {
                 let text_list_list = test_complex_list.reborrow().init_text_list_list(1);
-                text_list_list.init(0, 1).set(0, "abc");
+                text_list_list.init(0, 1).set(0, b"abc".into());
             }
 
             {
@@ -467,7 +467,8 @@ mod tests {
         assert!(enum_list_list.get(1).unwrap().get(0) == Ok(AnEnum::Foo));
         assert!(enum_list_list.get(1).unwrap().get(1) == Ok(AnEnum::Qux));
 
-        assert!(
+        assert_eq!(
+            "abc",
             complex_list_reader
                 .get_text_list_list()
                 .unwrap()
@@ -475,7 +476,6 @@ mod tests {
                 .unwrap()
                 .get(0)
                 .unwrap()
-                == "abc"
         );
         assert!(
             complex_list_reader
@@ -571,7 +571,7 @@ mod tests {
 
             {
                 let mut sub_builder = test_defaults.reborrow().get_struct_field().unwrap();
-                sub_builder.set_text_field("garply");
+                sub_builder.set_text_field(b"garply".into());
             }
 
             assert_eq!(test_defaults.reborrow().get_bool_field(), false);
@@ -595,7 +595,7 @@ mod tests {
 
             {
                 let sub_builder = test_defaults.reborrow().get_struct_field().unwrap();
-                assert_eq!("garply", &*sub_builder.get_text_field().unwrap());
+                assert_eq!("garply", sub_builder.get_text_field().unwrap());
             }
         }
     }
@@ -778,7 +778,7 @@ mod tests {
 
         // Check setters
 
-        test_set.set_text("foo");
+        test_set.set_text(b"foo".into());
         test_set.set_data(&[42]);
         {
             let mut b = test_set.reborrow().init_list(3);
@@ -787,7 +787,10 @@ mod tests {
             b.set(2, 3);
         }
         test_set.reborrow().init_empty_struct();
-        test_set.reborrow().init_simple_struct().set_field("buzz");
+        test_set
+            .reborrow()
+            .init_simple_struct()
+            .set_field(b"buzz".into());
         {
             let mut b = test_set.reborrow().init_any();
             b.set_as("dyn")?;
@@ -825,7 +828,7 @@ mod tests {
         let unset_reader = test_unset.into_reader();
 
         assert!(unset_reader.get_text()?.is_none());
-        assert_eq!(set_reader.get_text()?, Some("foo"));
+        assert_eq!(set_reader.get_text()?, Some(capnp::text::Reader(b"foo")));
 
         assert!(unset_reader.get_data()?.is_none());
         assert_eq!(set_reader.get_data()?, Some(&[42][..]));
@@ -841,7 +844,7 @@ mod tests {
 
         assert!(unset_reader.get_simple_struct()?.is_none());
         let r = set_reader.get_simple_struct()?.expect("is some");
-        assert_eq!(r.get_field()?, Some("buzz"));
+        assert_eq!(r.get_field()?, Some(capnp::text::Reader(b"buzz")));
 
         assert!(unset_reader.get_any().is_none());
         assert!(set_reader.get_any().is_some());
@@ -858,7 +861,7 @@ mod tests {
         {
             let branded_field = branded.reborrow().init_branded_field();
             let mut foo = branded_field.init_generic_field();
-            foo.set_text_field("blah");
+            foo.set_text_field("blah".into());
         }
 
         let reader = branded.into_reader();
@@ -882,10 +885,10 @@ mod tests {
         let mut branded = message_for_brand.init_root::<brand_twice::Builder<'_>>();
         {
             let mut baz = branded.reborrow().init_baz_field();
-            baz.set_foo_field("blah").unwrap();
+            baz.set_foo_field("blah".into()).unwrap();
             let mut bar = baz.init_bar_field();
-            bar.set_text_field("some text");
-            bar.set_data_field("some data".as_bytes());
+            bar.set_text_field("some text".into());
+            bar.set_data_field(b"some data");
         }
 
         let reader = branded.into_reader();
@@ -904,7 +907,7 @@ mod tests {
                 .unwrap()
         );
         assert_eq!(
-            "some data".as_bytes(),
+            b"some data",
             reader
                 .get_baz_field()
                 .unwrap()
@@ -923,7 +926,11 @@ mod tests {
         let mut root: test_generics::Builder<'_, test_all_types::Owned, text::Owned> =
             message.init_root();
         init_test_message(root.reborrow().get_foo().unwrap());
-        root.reborrow().get_dub().unwrap().set_foo("Hello").unwrap();
+        root.reborrow()
+            .get_dub()
+            .unwrap()
+            .set_foo("Hello".into())
+            .unwrap();
         {
             let mut bar: ::capnp::primitive_list::Builder<'_, u8> =
                 root.reborrow().get_dub().unwrap().initn_bar(1);
@@ -1083,7 +1090,10 @@ mod tests {
         }
 
         assert_eq!(union_struct.reborrow().get_union0().has_u0f0sp(), false);
-        union_struct.reborrow().init_union0().set_u0f0sp("abcdef");
+        union_struct
+            .reborrow()
+            .init_union0()
+            .set_u0f0sp("abcdef".into());
         assert_eq!(union_struct.get_union0().has_u0f0sp(), true);
     }
 
@@ -1105,7 +1115,8 @@ mod tests {
                 panic!("")
             };
 
-            let test_union_defaults::inner2::C(Ok("grault")) = reader.get_inner2().which().unwrap()
+            let test_union_defaults::inner2::C(Ok(capnp::text::Reader(b"grault"))) =
+                reader.get_inner2().which().unwrap()
             else {
                 panic!("")
             };
@@ -1214,8 +1225,8 @@ mod tests {
             let mut old_version = message.init_root::<test_old_version::Builder<'_>>();
             old_version.set_old1(123);
             let mut names = old_version.init_old4(2);
-            names.set(0, "alice");
-            names.set(1, "bob");
+            names.set(0, "alice".into());
+            names.set(1, "bob".into());
         }
         {
             let mut new_version = message.get_root::<test_new_version::Builder<'_>>().unwrap();
@@ -1227,11 +1238,8 @@ mod tests {
 
             let mut names = new_version.get_old4().unwrap();
             assert_eq!(names.len(), 2);
-            assert_eq!(
-                names.reborrow().get(0).get_text_field().unwrap().as_ref(),
-                "alice"
-            );
-            assert_eq!(names.get(1).get_text_field().unwrap().as_ref(), "bob");
+            assert_eq!(names.reborrow().get(0).get_text_field().unwrap(), "alice");
+            assert_eq!(names.get(1).get_text_field().unwrap(), "bob");
         }
     }
 
@@ -1244,8 +1252,8 @@ mod tests {
             let mut new_version = message.init_root::<test_new_version::Builder<'_>>();
             new_version.set_old1(123);
             let mut names = new_version.init_old4(2);
-            names.reborrow().get(0).set_text_field("alice");
-            names.get(1).set_text_field("bob");
+            names.reborrow().get(0).set_text_field("alice".into());
+            names.get(1).set_text_field("bob".into());
         }
         {
             let old_version = message
@@ -1316,9 +1324,9 @@ mod tests {
                     .reborrow()
                     .get_any_pointer_field()
                     .initn_as::<::capnp::text_list::Builder<'_>>(3);
-                list.set(0, "foo");
-                list.set(1, "bar");
-                list.set(2, "baz");
+                list.set(0, "foo".into());
+                list.set(1, "bar".into());
+                list.set(2, "baz".into());
             }
             {
                 let mut l = root
@@ -1326,9 +1334,9 @@ mod tests {
                     .get_as::<::capnp::struct_list::Builder<'_, test_lists::struct_p::Owned>>()
                     .unwrap();
                 assert_eq!(3, l.len());
-                assert_eq!("foo", &*l.reborrow().get(0).get_f().unwrap());
-                assert_eq!("bar", &*l.reborrow().get(1).get_f().unwrap());
-                assert_eq!("baz", &*l.reborrow().get(2).get_f().unwrap());
+                assert_eq!("foo", l.reborrow().get(0).get_f().unwrap());
+                assert_eq!("bar", l.reborrow().get(1).get_f().unwrap());
+                assert_eq!("baz", l.reborrow().get(2).get_f().unwrap());
             }
         }
     }
@@ -1376,10 +1384,7 @@ mod tests {
                 message.get_root().unwrap();
             assert_eq!(new_version.len(), 1);
             assert_eq!(new_version.reborrow().get(0).get_old1(), 0xab);
-            assert_eq!(
-                &*new_version.reborrow().get(0).get_old2().unwrap(),
-                "hello!!"
-            );
+            assert_eq!(new_version.reborrow().get(0).get_old2().unwrap(), "hello!!");
         }
 
         {
@@ -1918,7 +1923,7 @@ mod tests {
         let mut message = message::Builder::new_default();
         {
             let mut test = message.init_root::<test_all_types::Builder<'_>>();
-            test.set_text_field("Hello");
+            test.set_text_field("Hello".into());
         }
         let reader = message
             .get_root::<test_all_types::Builder<'_>>()
diff --git a/capnpc/test/test_util.rs b/capnpc/test/test_util.rs
index 317444bd6..c5f299f6b 100644
--- a/capnpc/test/test_util.rs
+++ b/capnpc/test/test_util.rs
@@ -34,7 +34,7 @@ pub fn init_test_message(mut builder: test_all_types::Builder<'_>) {
     builder.set_u_int64_field(12345678901234567890);
     builder.set_float32_field(1234.5);
     builder.set_float64_field(-123e45);
-    builder.set_text_field("foo");
+    builder.set_text_field("foo".into());
     builder.set_data_field(b"bar");
     {
         let mut sub_builder = builder.reborrow().init_struct_field();
@@ -50,14 +50,14 @@ pub fn init_test_message(mut builder: test_all_types::Builder<'_>) {
         sub_builder.set_u_int64_field(345678901234567890);
         sub_builder.set_float32_field(-1.25e-10);
         sub_builder.set_float64_field(345f64);
-        sub_builder.set_text_field("baz");
+        sub_builder.set_text_field("baz".into());
         sub_builder.set_data_field(b"qux");
         {
             let mut sub_sub_builder = sub_builder.reborrow().init_struct_field();
-            sub_sub_builder.set_text_field("nested");
+            sub_sub_builder.set_text_field("nested".into());
             sub_sub_builder
                 .init_struct_field()
-                .set_text_field("really nested");
+                .set_text_field("really nested".into());
         }
         sub_builder.set_enum_field(TestEnum::Baz);
 
@@ -157,15 +157,15 @@ pub fn init_test_message(mut builder: test_all_types::Builder<'_>) {
             struct_list
                 .reborrow()
                 .get(0)
-                .set_text_field("x structlist 1");
+                .set_text_field("x structlist 1".into());
             struct_list
                 .reborrow()
                 .get(1)
-                .set_text_field("x structlist 2");
+                .set_text_field("x structlist 2".into());
             struct_list
                 .reborrow()
                 .get(2)
-                .set_text_field("x structlist 3");
+                .set_text_field("x structlist 3".into());
         }
 
         let mut enum_list = sub_builder.reborrow().init_enum_list(3);
@@ -189,9 +189,18 @@ pub fn init_test_message(mut builder: test_all_types::Builder<'_>) {
 
     {
         let mut struct_list = builder.reborrow().init_struct_list(3);
-        struct_list.reborrow().get(0).set_text_field("structlist 1");
-        struct_list.reborrow().get(1).set_text_field("structlist 2");
-        struct_list.reborrow().get(2).set_text_field("structlist 3");
+        struct_list
+            .reborrow()
+            .get(0)
+            .set_text_field("structlist 1".into());
+        struct_list
+            .reborrow()
+            .get(1)
+            .set_text_field("structlist 2".into());
+        struct_list
+            .reborrow()
+            .get(2)
+            .set_text_field("structlist 3".into());
     }
 
     // ...
@@ -218,7 +227,7 @@ check_test_message_impl(($mod:ident::$typ:ident) => (
             assert_eq!(12345678901234567890, reader.reborrow().get_u_int64_field());
             assert_eq!(1234.5, reader.reborrow().get_float32_field());
             assert_eq!(-123e45, reader.reborrow().get_float64_field());
-            assert_eq!("foo", &*reader.reborrow().get_text_field().unwrap());
+            assert_eq!("foo", reader.reborrow().get_text_field().unwrap());
             assert_eq!(b"bar", &*reader.reborrow().get_data_field().unwrap());
             {
                 let mut sub_reader = reader.reborrow().get_struct_field().unwrap();
@@ -234,13 +243,14 @@ check_test_message_impl(($mod:ident::$typ:ident) => (
                 assert_eq!(345678901234567890, sub_reader.reborrow().get_u_int64_field());
                 assert_eq!(-1.25e-10, sub_reader.reborrow().get_float32_field());
                 assert_eq!(345f64, sub_reader.reborrow().get_float64_field());
-                assert_eq!("baz", &*sub_reader.reborrow().get_text_field().unwrap());
+                assert_eq!("baz", sub_reader.reborrow().get_text_field().unwrap());
                 assert_eq!(b"qux", &*sub_reader.reborrow().get_data_field().unwrap());
                 {
                     let mut sub_sub_reader = sub_reader.reborrow().get_struct_field().unwrap();
-                    assert_eq!("nested", &*sub_sub_reader.reborrow().get_text_field().unwrap());
-                    assert_eq!("really nested", &*sub_sub_reader.get_struct_field().unwrap()
-                                                                .get_text_field().unwrap());
+                    assert_eq!("nested", sub_sub_reader.reborrow().get_text_field().unwrap());
+                    assert_eq!("really nested",
+                               sub_sub_reader.get_struct_field().unwrap()
+                               .get_text_field().unwrap());
                 }
                 assert!(Ok(TestEnum::Baz) == sub_reader.reborrow().get_enum_field());
                 assert_eq!(false, sub_reader.reborrow().has_interface_field());
@@ -355,9 +365,9 @@ check_test_message_impl(($mod:ident::$typ:ident) => (
                 {
                     let mut struct_list = sub_reader.reborrow().get_struct_list().unwrap();
                     assert_eq!(3, struct_list.len());
-                    assert_eq!("x structlist 1", &*struct_list.reborrow().get(0).get_text_field().unwrap());
-                    assert_eq!("x structlist 2", &*struct_list.reborrow().get(1).get_text_field().unwrap());
-                    assert_eq!("x structlist 3", &*struct_list.reborrow().get(2).get_text_field().unwrap());
+                    assert_eq!("x structlist 1", struct_list.reborrow().get(0).get_text_field().unwrap());
+                    assert_eq!("x structlist 2", struct_list.reborrow().get(1).get_text_field().unwrap());
+                    assert_eq!("x structlist 3", struct_list.reborrow().get(2).get_text_field().unwrap());
                 }
 
                 {
@@ -386,9 +396,9 @@ check_test_message_impl(($mod:ident::$typ:ident) => (
             {
                 let mut struct_list = reader.reborrow().get_struct_list().unwrap();
                 assert_eq!(3, struct_list.len());
-                assert_eq!("structlist 1", &*struct_list.reborrow().get(0).get_text_field().unwrap());
-                assert_eq!("structlist 2", &*struct_list.reborrow().get(1).get_text_field().unwrap());
-                assert_eq!("structlist 3", &*struct_list.reborrow().get(2).get_text_field().unwrap());
+                assert_eq!("structlist 1", struct_list.reborrow().get(0).get_text_field().unwrap());
+                assert_eq!("structlist 2", struct_list.reborrow().get(1).get_text_field().unwrap());
+                assert_eq!("structlist 3", struct_list.reborrow().get(2).get_text_field().unwrap());
 
             }