Simple Generics problem

Hi. I'm new to Rust and just signed up in this forum. Looking forward to my explorations in this language. Forgive me if this question is too basic for this forum. I'm trying to write a simple generic function something like this:

fn myfunc<T> (v: &Vec<T>) -> Vec<T>
    where T: std::ops::Mul 
{
      let result : Vec<T> = v.iter().map(|x| x * x).collect();
      result
}

but get the compiler error binary operation * cannot be applied to type &T
I know this is because iter() gives me a reference to the underlying type but don't know how else to write it (using ref x or *x doesn't seem to help). Thanks for your help

The simplest solution is to require Clone:

fn myfunc<T> (v: &Vec<T>) -> Vec<T>
where T: Clone + std::ops::Mul<Output=T>,
{
      let result : Vec<T> = v.iter().map(|x| x.clone() * x.clone()).collect();
      result
}
2 Likes

You can make the lifetime of the borrow explicit, or use HRTB:

fn myfunc<'borrow, T : 'borrow> (v: &'borrow [T]) -> Vec<T>
where
    &'borrow T : std::ops::Mul<Output = T>,
{
      let result : Vec<T> = v.iter().map(|x| x * x).collect();
      result
}
2 Likes

Thanks! This works. I don't really understand the logic of it - the compiler performs a deref coercion only if the lifetime of the borrow is explicit? Is it something to do with the compiler being unable to infer the reference type (and hence if it implements Deref)? But how does adding explicit lifetime bounds make the compiler any wiser?

Thanks. I was only able to get this to work by adding this line to the trait bounds (I'm using version 1.35.0) :
where std::vec::Vec<T>: std::iter::FromIterator<<T as std::ops::Mul>::Output>

Take another look at my snippet:

where T: Clone + Mul<T, Output=T>
1 Like

The thing is that the product you wrote in the iterator used a borrow to a T as both the left and right operands (and had to output an owned T).

Hence the bound &T : Mul<Output = T>.

But if you try writing that down it will complain about a missing lifetime parameter:

That happens because there is no such thing as the &T type. Try writing type Ref<T> = &T; and you will see that Rust will complain that the right hand side is not a type: it is missing a lifetime parameter.

The type of (shared) borrows over a value of type T for the lifetime 'borrow is &'borrow T. That is an actual type.

  • (if T itself was a borrow (e.g., T = &'inner String), then for &'borrow &'inner String to be valid, the lifetime 'inner must include / be at least as big as 'borrow. This constraint is written 'inner : 'borrow, and more generally, for a generic T, it is written T : 'borrow.)
type Ref<'borrow, T> = &'borrow T;
  • so the &'_ _ type constructor has an arity of 2: a lifetime parameter, and a type parameter.

There are, however, many places where the lifetime may be elided, such as when annotating the types in a let binding, in the arguments of a function, or in a function's return type. This is possible because there are very explicit "lifetime elision" rules that allow Rust to cut us some slack.

What may be surprising is that there are cases where there are real ambiguities,, and then Rust will force you to make the lifetime parameters explicit.

This is the case in trait bounds: when writing down &T : Mul..., there are many different interpretations to the bound, depending on the value of the elided lifetime parameter.

For instance, three notable cases come to mind:

  • &'static T : Mul..., i.e., only infinite borrows can be multiplied;

  • &'input T : Mul..., i.e., only borrows borrowed for exactly the same lifetime as the input vec can be multiplied;

    • Note that for 'input or 'borrow or 'a to exist, it needs to be a generic parameter of the function (or the trait containing the function). If, moreover, you wish that that lifetime parameter refer to the borrow of the input Vec, then the lifetime parameter of that argument can no longer be elided, since it must use the parameter explicitely. In my answer I had called it 'borrow
  • or all the references to a T, no matter the lifetime, can be multiplied. This universal quantification over a parameter is called in rust Higher Rank Trait Bounds, and currently only supports unbounded lifetime parameters:

    for<'x> &'x T : Mul<Output = T>
    

    (This works for your function, but there was a bug that made the compilation crash when we monomorphised to a specific non-'static type (I don't know if it has been fixed), so I try to avoid it when it is bot necessary).

As you can see, there is no obvious answer, so Rust wants the programmer to specify their wish.


A simpler solution to you code (but less generic) is to add a T : Copy bound, and use a dereferencing pattern |&x| as the argument of the closure. This way, no borrows (and thus no lifetimes!) are involved.

3 Likes

Ah thanks. Quite clear now

This topic was automatically closed 90 days after the last reply. New replies are no longer allowed.