Rust guarantees that Box<T>
is representationally equivalent to &T
.
Given a &Box<T>
, there isn't much you can do except to extract &T
from it. So would it be fair to say that a function that operates on a &Box<T>
is representationally equivalent to a function that operates on &&T
instead? Can this be extended to aggregate data structures such as &(Box<Box<T>>, &mut U)
vs &(&&T, &U)
? What about custom structs and enums?
This came to mind when thinking about how ownership considerations of composite data structures can limit API design in Rust. Currently, either the library author has to carefully plan out the ownership of all data structures, which limits the user's flexibility, or they have to expose complex, generic-laden APIs to permit ownership flexibility.
For example, a lot of the time when a complicated struct is declared, e.g.
struct MyConfig {
name: String,
items: Vec<String>,
...
}
impl MyConfig {
fn my_api(&self) { ... }
}
the library only wants to read the data, so it ends up not really caring whether the client provides name: String
or name: &str
, or whether Vec<String>
or Vec<&str>
or &[&str]
is given. Yet, the naive approach would limit what the user can provide. If the library author wanted more flexibility, they would have to write something terrible like:
struct MyConfig<N, I, ...> {
name: N,
items: I,
...
}
impl<N: Deref<Target=str>, I: Deref<Target=[impl Deref<Target=str>]>, ...> MyConfig<N, I> {
fn my_api(&self) { ... }
}
In a hypothetical Rust variant where &str
is representationally compatible with and coercible [1] from String
, and similarly for &[T]
from Vec<T>
, then it wouldn't be necessary to use generics for this at all, which introduces both mental complexity as well as needless code bloat. A single function ought to be able to cover all possible ownership configurations.
[1]: Sadly this doesn't work in real Rust because String
has an extra field compared to &str
.