1

I'm creating a list-like structure with an enum:

#[derive(Debug, PartialEq, Eq)]
enum Cons<T: Clone> {
  Cons(T, Box<Cons<T>>),
  Null
}

However, I'm having trouble making a map method for this:

  pub fn map<F,S>(&self, fun: F) -> Cons<S>
    where F: Fn(T) -> S, S: Clone
  {
    match self {
      &Cons::Null => Cons::Null,
      &Cons::Cons(ref head, ref tail) => Cons::new(fun(*head), tail.map(fun)),
    }
  }

I'll get this:

error[E0507]: cannot move out of `*head` which is behind a shared reference
  --> src/lib.rs:50:56
   |
50 |       &Cons::Cons(ref head, ref tail) => Cons::new(fun(*head), tail.map(fun)),
   |                                                        ^^^^^ move occurs because `*head` has type `T`, which does not implement the `Copy` trait

After some attempts mixing and matching referencing and dereferencing, I gave up and used clone:

&Cons::Cons(ref head, ref tail) => Cons::new(fun(head.clone()), tail.map(fun)),

While it works, I'm not quite sure why the value has moved. I read the chapter on references and borrowing in the Rust book, but I didn't find anything there. Also, I have read about the ref keyword, but it just seems like the same thing as &, but in destructuring. I even looked at Rust by example for moves and I still don't understand where my head has gone to. So uh, can you help me find where my head went?

Playground

I do understand what borrowing/moves is/are, I just don't understand why it has happened here.

kelsny
  • 23,009
  • 3
  • 19
  • 48
  • That's another option on the table yes, but I *really* want to know where my `head` has run off to. I don't see anywhere `head` was used before the call to `fun`. Is it because I used `self` already? – kelsny Oct 07 '22 at 15:23
  • 1
    The receiver of your `map` function is `&self` so you don't own your `Cons` instance. Therefore you can't move any of the internal values of your borrowed `Cons` into the returned `Cons`, which is what you're trying to do when you dereference `*head`. – jsstuball Oct 07 '22 at 15:31
  • So it moved because I tried to "steal" it instead. I didn't think about what `&self` had to do with this. If you add an answer I'll accept it, thanks. – kelsny Oct 07 '22 at 15:39

1 Answers1

2

The function you take has type Fn(T) -> S, it thus consumes the T to create the S (as a map function should do). You have two options: either

  1. change the function type to Fn(&T) -> S or
  2. keep the function type as is and change the signature to take self instead of &self.

I would prefer the second solution, as it is the most idiomatic and behaves like options, iterators, etc.

Full code example:

#[derive(Debug, PartialEq, Eq)]
enum Cons<T> {
  Cons(T, Box<Cons<T>>),
  Null
}

impl<T> Cons<T> {
 pub fn map<F,S>(self, fun: F) -> Cons<S>
    where F: Fn(T) -> S
  {
    match self {
      Cons::Null => Cons::Null,
      Cons::Cons(head, tail) => Cons::Cons(fun(head), Box::new(tail.map(fun))),
    }
  }
}

Try it online!

leo848
  • 637
  • 2
  • 7
  • 24