|
| 1 | +--- |
| 2 | +layout: post |
| 3 | +title: 0.19 — setter ergonomics and faster reflection |
| 4 | +author: dwrensha |
| 5 | +--- |
| 6 | + |
| 7 | +As of today, |
| 8 | +version 0.19 of [capnproto-rust](https://github.com/capnproto/capnproto-rust) |
| 9 | +is [available on crates.io](https://crates.io/crates/capnp). |
| 10 | + |
| 11 | +This release includes improved ergnomics and performance, |
| 12 | +while also having a notable breaking change involving text fields. |
| 13 | + |
| 14 | + |
| 15 | +## setter ergonomics |
| 16 | + |
| 17 | +Suppose that we have the following struct defined in a Cap'n Proto schema file: |
| 18 | + |
| 19 | +```capnp |
| 20 | +struct Cookie { |
| 21 | + fortune @0 :Text; |
| 22 | + numbers @1 :List(UInt16); |
| 23 | +} |
| 24 | +
|
| 25 | +``` |
| 26 | + |
| 27 | +With capnp-v0.18.0 (the previous release), to populate such a struct you would write Rust code like this: |
| 28 | + |
| 29 | +```rust |
| 30 | +let mut message = capnp::message::Builder::new_default(); |
| 31 | +let mut root: cookie::Builder = message.init_root(); |
| 32 | +root.set_fortune("This too shall pass.".into()); |
| 33 | +let mut numbers = root.init_numbers(6); |
| 34 | +numbers.set(0, 4); |
| 35 | +numbers.set(1, 8); |
| 36 | +numbers.set(2, 15); |
| 37 | +numbers.set(3, 16); |
| 38 | +numbers.set(3, 23); |
| 39 | +numbers.set(3, 42); |
| 40 | +``` |
| 41 | + |
| 42 | +This is rather more verbose than you might hope. |
| 43 | +The setter methods `set_fortune()` and `set_numbers()` are geared toward |
| 44 | +accepting input from *other* Cap'n Proto messages, rather than |
| 45 | +from Rust-native values. |
| 46 | +When we want to call `set_fortune()` on a Rust-native `&str`, |
| 47 | +we first need to convert it into a `capnp::text::Reader` via the `.into()` method. |
| 48 | +Similarly, the `set_numbers()` method wants a `primitive_list::Reader<u16>`, |
| 49 | +and there is no easy way for us to get one of those from a Rust-native `&[u16]`. |
| 50 | +Therefore, we avoid that method altogether, and instead opt to use `init_numbers()` |
| 51 | +and to invidually set each element of the list. |
| 52 | + |
| 53 | + |
| 54 | +In capnp-v0.19.0, we can instead directly set these fields from Rust-native values: |
| 55 | + |
| 56 | +```rust |
| 57 | +let mut message = capnp::message::Builder::new_default(); |
| 58 | +let mut root: cookie::Builder = message.init_root(); |
| 59 | +root.set_fortune("This too shall pass."); |
| 60 | +root.set_numbers(&[4, 8, 15, 16, 23, 42]); |
| 61 | +``` |
| 62 | + |
| 63 | +This is possible because the setter methods have been generalized |
| 64 | +to accept a value of type `impl SetterInput<T>`, as follows: |
| 65 | + |
| 66 | +```rust |
| 67 | + |
| 68 | +mod cookie { |
| 69 | + impl <'a> Builder<'a> { |
| 70 | + pub fn set_fortune(&mut self, impl SetterInput<capnp::text::Owned>) { |
| 71 | + ... |
| 72 | + } |
| 73 | + pub fn set_numbers(&mut self, |
| 74 | + impl SetterInput<capnp::primitive_list::Owned<u16>>) { |
| 75 | + ... |
| 76 | + } |
| 77 | + } |
| 78 | +} |
| 79 | + |
| 80 | +``` |
| 81 | + |
| 82 | +The trait `SetterInput<capnp::text::Owned>` is implemented both by |
| 83 | +`capnp::text::Reader` and by `&str`, and |
| 84 | +the trait `SetterInput<capnp::primitive_list::Owned<u16>>` |
| 85 | +is implemented by both `capnp::primitive_list::Reader<u16>` |
| 86 | +and by `&[u16]`. |
| 87 | + |
| 88 | +### breaking change |
| 89 | + |
| 90 | +Unfortunately, this generalization does cause some breakage. |
| 91 | +If we did not update the old line |
| 92 | +```rust |
| 93 | +root.set_fortune("This too shall pass.".into()); |
| 94 | +``` |
| 95 | +then it would now gives us a type error: |
| 96 | + |
| 97 | +``` |
| 98 | +error[E0283]: type annotations needed |
| 99 | +... |
| 100 | + = note: multiple `impl`s satisfying `_: SetterInput<capnp::text::Owned>` found in the `capnp` crate: |
| 101 | + - impl<'a> SetterInput<capnp::text::Owned> for &'a String; |
| 102 | + - impl<'a> SetterInput<capnp::text::Owned> for &'a str; |
| 103 | + - impl<'a> SetterInput<capnp::text::Owned> for capnp::text::Reader<'a>; |
| 104 | +note: required by a bound in `cookie::Builder::<'a>::set_fortune` |
| 105 | +
|
| 106 | +``` |
| 107 | + |
| 108 | +The problem is that `.into()` does not know which type to target. |
| 109 | +The fix is to remove the `.into()`. |
| 110 | + |
| 111 | +Note that the need for such `.into()` calls was in fact only recently |
| 112 | +introduced, in the release of |
| 113 | +[version 0.18]({{site.baseurl}}/2023/09/04/0.18-release.html). |
| 114 | +Probably we should have |
| 115 | +delayed that release until we had a solution like the |
| 116 | +the present `impl SetterInput` generalization, |
| 117 | +thereby minimizing the churn of downstream code. |
| 118 | + |
| 119 | + |
| 120 | +## faster reflection |
| 121 | + |
| 122 | +The [0.17 release]({{site.baseurl}}/2023/05/08/run-time-reflection.html) |
| 123 | +added support for run-time reflection, |
| 124 | +including a `DynamicStruct` type that supports |
| 125 | +looking up fields by name. |
| 126 | +The initial implementation |
| 127 | +worked by linearly scanning a struct's fields. |
| 128 | +That works fine for small structs, but can |
| 129 | +get expensive when there are a large number of fields. |
| 130 | + |
| 131 | +In [#469](https://github.com/capnproto/capnproto-rust/pull/469), |
| 132 | +[@quartox](https://github.com/quartox) updated |
| 133 | +the implementation to use binary search, |
| 134 | +resulting in a significant performance increase, |
| 135 | +and matching the capnproto-c++ implementation. |
| 136 | + |
| 137 | +This change involved add a new field to the static `RawStructSchema` value included |
| 138 | +in the generated code for each Cap'n Proto type. |
| 139 | + |
0 commit comments