'Is there an idiomatic way to have a generic over T, &T, Box<T>, Arc<T>, and so forth, where T implements some trait R?
Let's say you have a structure that you want to be a generic over time type T
, so long as that type implements some trait R
. That's pretty trivial:
trait R {
fn func_a(&self);
fn func_b(&mut self);
}
struct A;
impl R for A {
fn func_a(&self) {}
fn func_b(&mut self) {}
}
struct B<T>(T) where T: R;
fn main() {
let _ = B(A);
// let _ = B(&A); // Error: the trait `R` is not implemented for `&A`
}
But this gets more complicated if you want B
to also accept references (&A
) or smart pointers (Box<A>
, Arc<A>
, etc...) One approach is to impl R
for references specific smart pointers. For example,
impl R for &A { /*...*/ }
impl R for Box<A> { /*...*/ }
// etc...
While this approach seems simple, it doesn't work when R
has methods that take a &mut self
because of Rust's mutability rules. And, even if all R
's methods do take a &self
, the set of smart pointers that can be used are limited to those you explicity put in implementations for, and your code is littered with proxy implementations.
The alternative approach is to use the Deref
trait. This works for all the smart pointers at once, but no longer works for the simple owned value. Of course, if you want the resulting B
to have a 'static
lifetime, it's trivial to create a new Box
, but this also means an unnecessary memory allocation.
struct B<T>(T) where T: Deref, T::Target: R;
fn main() {
// let _ = B(A); // Error: the trait bound `A: Deref` is not satisfied
let _ = B(&A);
let _ = B(Box::new(A));
}
Finally, we have the Borrow
trait. Becuase the standard library includes a blanket impl Borrow<T> for T
, as well as the smart pointers, but this now becomes rather awkward to use. Our structure B
needs to have a PhantomData<U>
, and our uses need type annotations or they don't compile.
struct B<T, U>(T, PhantomData<U>) where T: Borrow<U>, U: R;
fn main() {
let _ = B::<&A, A>(&A, Default::default());
let _ = B::<Box<A>, A>(Box::new(A), Default::default());
let _ = B(A, Default::default());
}
Of these options, using the Deref
trait seems to the cleanest. You can even make it work with an owned value by creating a trivial wrapper type like this OwnedCell
.
struct B<T>(T) where T: Deref, T::Target: R;
struct OwnedCell<T>(T);
impl<T> Deref for OwnedCell<T> {
type Target = T;
fn deref(&self) -> &Self::Target {
&self.0
}
}
fn main() {
let _ = B(&A);
let _ = B(Box::new(A));
let _ = B(OwnedCell(A));
}
However, the fact that there doesn't seem to be anything like OwnedCell
in the standard library makes me think I might be missing something here. What's the right way to do this?
Sources
This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.
Source: Stack Overflow
Solution | Source |
---|