So the use case is pretty simple, I'm writing something like a maths library. In general however the carrier expression wrapper can not be copied as it contains an Rc to a common state:
Because of this most functions take by reference rather than value. Each function however returns by value. The main issue is with temporary expressions e.g. you can not write tanh(&a + &b) because the return of the sum is a temporary, but actually a value so you have to do tanh(&(&a + &b)) which is a bit ugly. I was wondering if there is anyway to implement a function which can accept both by reference and by value?
PS: Consider that the logic is such that if I call by value I will just call again the function which accepts by reference (e.g. this is known prior) :
I think I got it. It would be nice if we have a placeholder which explicitely says I can be either a value or a reference built in to the language. Has anyone proposed anything like that?
Is Borrow really correct here? I'd think AsRef is more appropriate. Borrow requires that Borrowed and Self have equal Hash, Eq, and Ord (if any of these are implemented), which seem like unnecessary/extra requirements for this case (even if they're not used). It does enable more flexible borrowing (the classic example is allowing &str keys in HashMap<String, _> lookups), but I'm not sure it's needed here. It seems that AsRef is. Am I missing something?
I'm starting to get a bit confused by all this. I think I understood what is the difference between Borrow and AsRef that is Borrow gives you stronger guarantees such as ordering and hashing, while AsRef does not. I'm actually not sure why Borrow does not require AsRef by T and was not made to be depend on it. But anyway how is Cow getting in to the picture here?
I believe that part is correct. Borrow is also more flexible because it allows you to borrow different types from Self, e.g. String implements Borrow<str> allowing a HashMap<String, _> to work with &str lookup keys, for example. But I think primarily it's the extra guarantees (or promises, rather) of Borrow that set it apart.
Taking a Cow<Expr> would let you work with a borrowed or owned Expr as well, I believe, so thought I'd mention it. It's possible I'm missing something though, as I mentioned, so happy to have anyone clarify.
Is my understanding of Cow correct, which is that it is sort of like AsRef however you are allowed to do mutable stuff in which case it clones the object?
I think you could say that, yeah - at least it sounds sensible to me. Cow actually works with Borrow somewhat by virtue of requiring the generic type param of Cow to impl ToOwned, which in turn has an associated type that imps Borrow . So actually in light of that, Cow is probably overkill here (and would require Expr to be Clone or ToOwned), and it comes down to Borrow vs AsRef.
I don't think Cow is really the right thing here; it seems like AsRef does exactly what you want.
Here's an example using a toy Expr type which is not Copy but which does implement Clone. The it_works function shows the type in action.
#![cfg(test)]
#[derive(Clone)]
struct Expr(String);
// This accepts expressions by reference or by value, by being always prepared
// to work with a reference, and then accepting anything such a reference could
// be borrowed from.
fn tanh<E: AsRef<Expr>>(expr: E) -> Expr {
let expr = expr.as_ref();
Expr(format!("tanh({})", expr.0))
}
// This ensures that you can pass Expr by value.
impl AsRef<Expr> for Expr {
fn as_ref(&self) -> &Expr { self }
}
// These two `Add` impls are kind of icky, but show how you can make things
// reasonably legible. I wasn't able to get a version of this working with
// `AsRef`, but you can write out the impls explicitly.
impl std::ops::Add<Expr> for Expr {
type Output = Expr;
fn add(self, rhs: Expr) -> Expr {
&self + &rhs
}
}
impl<'a> std::ops::Add<&'a Expr> for &'a Expr {
type Output = Expr;
fn add(self, rhs: &'a Expr) -> Expr {
Expr(format!("{} + {}", self.0, rhs.0))
}
}
#[test]
fn it_works() {
let x = Expr("2π".to_string());
let y = Expr("0.5".to_string());
// Outer call takes by value, inner by reference.
let z = tanh(x + tanh(&y));
assert_eq!(z.0, "tanh(2π + tanh(0.5))");
}
impl<T> Borrow<T> for T where T: ?Sized
impl<'a, T> Borrow<T> for &'a T where T: ?Sized
impl<'a, T> Borrow<T> for &'a mut T where T: ?Sized
but those are not implemented for AsRef by default? If really AsRef provide similar functionality to Borrow but with less guarantees I see noreason for this. The thing is I have one method that accepts [bool;4] which can be either by reference or value, but that is impossible with AsRef, but Borrow works?
E.g. I get:
the trait `std::convert::AsRef<[bool; 4]>` is not implemented for `[bool; 4]`
I mean principly, this is like nothing significant. I was just not understading why this can be achieved via Borrow, .e.g the signiture to be T: Borrow<[bool; 4]>but not with AsRef.Is there any goo reason why (because if my understanding is correct such reason does not exist)
I'd love to know that myself actually - I noticed the difference between the set of builtin impls of Borrow and AsRef as well. My guess is those Borrow impls exist to support the usecases where Borrow is used, such as in HashMap. In particular, impl Borrow for T probably allows HashMap::get to just work with Ts itself (e.g. querying a 'HashMap<String, _>' with String keys, and not just slices).