I am trying to create a struct that contains something that implements a trait which can be either an owned type or a ref-counted pointer such as Arc, Rc, etc. So far I have implemented the following using the Borrow trait, however, the compiler is forcing me to annotate the types of the instances returned by the new function, which is not ideal for the users of my crate.
How can I modify the following code, so that the user does not need to annotate the types of the instances returned by the new function?
struct Foo<B, T> {
borrow: B,
_marker: std::marker::PhantomData<T>
}
impl<B, T> Foo<B, T> {
fn new(borrow: B) -> Self {
Self {
borrow,
_marker: Default::default()
}
}
}
impl <B, T> Foo<B, T> where
B: std::borrow::Borrow<T>,
T: std::fmt::Display {
fn print(&self) {
println!("{}", self.borrow.borrow())
}
}
fn main() {
// owned string (remove type annotation for error)
let f: Foo<_, String> =
Foo::new(String::from("hello"));
f.print();
// string slice (remove type annotation for error)
let f: Foo<_, &str> =
Foo::new("hello");
f.print();
// string in Arc (remove type annotation for error)
let f: Foo<_, std::sync::Arc<String>> =
Foo::new(std::sync::Arc::new(String::from("hello")));
f.print();
}
The problem with your code is that a type, your
Bcan implementBorrow<T>for many differentTtypes, so if you don't specify theTsomehow, the call will be ambiguous. The same is true forAsRef.But it is not for
Deref. That can only be implemented once per type, and it is already implemented for anystdsmart pointer.The code is straightforward, because you can constrain the
Deref::Targettype:This has the drawback that will only work for smart-pointer-like types. That is if for example
B = i32it will not work because a plain type such asi32doesn't implementDeref:If that is ok for you, you can stop reading here.
If that is not ok, I think the best option is to forget the
AsRef/Deref/Borrowthing and just require thatBimplements either the trait you want, or a facade for that trait.The details would vary depending on exactly what you want to implement. If your trait is actually
Displayand that is not a placeholder, then you don't actually need anything special, becauseDisplayis implemented forBox<T>,Rc<T>,Arc<T>, etc. whenT: Display. So you just write:But I'm guessing that your actual trait is not
Display. Again the details vary depending on whether you own that trait or not. Imagine this trait:If it is yours you can write the following code directly, but if it is third-party you will need a facade trait:
And your
Foowould be:Then you can implement the trait for any types you want. The small inconvenient here is that you would like two write two blanket implementations:
But only one or the other is allowed, Rustc will not allow both because of ambiguity. If needed, this can be fixed with a few manual implementations for the needed wrappers. Or a new-type to replace the first blanket impl, such as this one:
Here is a playground with the whole example.