Is it possible to define a function which accepts both by ref and by value


#1

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:

struct Expr {
    pub rc: Rc<CommonState>,
    pub id: usize,
}

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) :

pub fn tanh(e: &Expr) -> Expr {//*do stuff }
pub fn tanh(e: Expr) -> Expr{ tanh(&e) }

#2

You can use a trait to do it. I don’t believe there is one in the standard library for it. Example implementation

trait ValueOrReferenceTo<T> {
    fn as_ref(&self) -> &T;
}

impl<'a, T> ValueOrReferenceTo<T> for &'a T {
    fn as_ref(&self) -> &T {
        *self
    }
}

impl<T> ValueOrReferenceTo<T> for T {
    fn as_ref(&self) -> &T {
        self
    }
}

fn test<T: ValueOrReferenceTo<i32>>(val: T) -> i32 {
    *val.as_ref() + *val.as_ref()
}

fn main() {
    println!("{}", test(2));
    println!("{}", test(&2));
}

#3

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?


#4

Wouldn’t std::borrow::Borrow do the trick?


#5

I think you want Borrow. Sometimes AsRef is more correct, but I don’t think this is one of those times.


#6

Yes I think Borrow is what I needed and what minno implemented in fact. Thanks.


#7

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?


#8

Also, if generic code isn’t needed, like the tanh example, then Cow can be used, I imagine.


#9

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?


#10

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.


#11

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?


#12

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 :slight_smile:. 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.


#13

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))");
}

[edit: simplify lifetimes in second Add impl]


#14

Hmm one starnge thing is that we have:

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]`

#15

Why do you need AsRef in that case? It sounds like your method isn’t generic. Can you take the array by ref always?


#16

Well I should accept only smth that can has 4 elements can be treated as bool.

The only generic thing I want to do is to have a single fucntion taking &[bool; 4] and [bool; 4].


#17

I mean why can’t the function look like this:

fn foo(b: & [bool; 4]) {}

When you have an owned value, you can just borrow it for that call. I might be missing something from your scenario.


#18

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)


#19

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).


#20

@llogiq has a blog post about these traits that you might find helpful.