04 Sep 2023
New release alert! Version 0.18 of capnproto-rust is now available on crates.io.
If you use capnproto-rust on data with
the Text
built-in type,
then it’s likely that this release will require some
updates to your code.
But don’t worry — the changes are straightforward and they bring some
important benefits.
Suppose we have the following struct defined in a Cap’n Proto schema:
struct Foo {
oneText @0 :Text;
anotherText @1 :Text;
}
Then, in Rust, these Text
fields can be accessed through the text::Reader
type:
let my_foo: foo::Reader = ...;
let one_text: capnp::text::Reader<'_> = my_foo.get_one_text()?;
let another_text: capnp::text::Reader<'_> = my_foo.get_another_text()?;
But what exactly is a text::Reader
?
In previous versions of capnproto-rust, the text::Reader
type
was an alias to Rust’s &str
type:
pub mod text {
type Reader<'a> = &'a str;
}
At first glance, this seems like a perfect fit.
A Cap’n Proto Text
value is required to
contain valid UTF-8 data, just like a Rust &str
,
and a text::Reader
is meant to represent
a reference to that data.
However, in practice, there are some ways in which this representation falls short.
text::Reader
is just
&str
, then we need to validate every time that we:
This goes against the general Cap’n Proto philosophy of doing validation as lazily as possible.
To address the above-noted shortcomings,
version 0.18 of capnproto-rust defines text::Reader
like this:
pub mod text {
/// 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]);
impl<'a> Reader<'a> {
pub fn as_bytes(self) -> &'a [u8] { ... }
pub fn to_str(self) -> Result<&'a str, Utf8Error> { ... }
pub fn to_string(self) -> Result<String, Utf8Error> { ... }
}
impl<'a> From<&'a str> for Reader<'a> { ... }
impl<'a> From<&'a [u8]> for Reader<'a> { ... }}
}
Now consumers can easily access the underlying data, via as_bytes()
,
and getting it as a &str
or String
just requires an extra to_str()
or to_string()
call.
When setting text fields in a message, you will now need to
insert some .into()
calls to convert from a str
or String
into a text::Reader
, like this:
let name: &str = "alice";
let mut my_foo: foo::Builder = ...;
my_foo.set_one_text("hello world".into())?;
my_foo.set_another_text(format!("hello {name}")[..].into())?;
All this is admittedly more verbose than it was before, but it’s in keeping with the general spirit of capnproto-rust: we are willing to introduce some verbosity if that’s what it takes to model Cap’n Proto data in a satisfactory way.
Another new feature is no-alloc mode.
In version 0.13, capnproto-rust
gained support for no_std environments.
However, it still depended on the alloc
crate,
which can sometimes be a problem for microcontroller targets and kernel programming.
(See this issue
for some discussion.)
Starting with version 0.18, the capnp
crate now has an
alloc
Cargo feature,
which can be disabled to remove the alloc
dependency.
A side benefit of this change is that now error handling in capnproto-rust is much less dependent on heap allocation, and so should have better performance and be more reliable.