Why methods like Option::map take self instead of &self?


#1

Hi,

I was wondering why Iterator::map takes a closure that has a value as argument instead of a reference, then I gave a look into the code and I found out that it’s because Option::map (that is used internally) takes self instead of &self .
Anyway I cannot understand the reason of this choice. Why we want to move the value when we use map?
Generally speaking there is some guideline/good practice to decide when is better to prefer passing by reference or value?

Thank you.


#2

If Option methods don’t take self by value, then it’s impossible to call any methods on the inner value that take self by value. Option provides an as_ref method to allow callers to choose not to consume the inner value instead, on an as-needed basis.


#3

Your point makes sense, now I think I have understood. Thank you.

Anyway I was thinking that it is true since we want to avoid forcing that generic type derives the Copy trait, otherwise it would possible providing a reference. Let me explain better with the following example:

#[derive(Debug, Copy, Clone)]
// Useless enum... Just to prove my point
enum MyOption<T> where T: Copy {
    Some(T),
}

#[derive(Debug, Copy, Clone)]
struct Foo(i32);

impl <T> MyOption<T> {
    fn map(&self) -> T where T: Copy {
        let option = *self;
        match option {
            MyOption::Some(x) => x,
        }
    }
}

fn main() {
    let opt0 = MyOption::Some(5);
    let val0 = opt0.map();
    
    println!("{:?}", val0);
    
    let opt1 = MyOption::Some(Foo(10));
    let val1 = opt1.map();
    
    println!("{:?}", val1);
}

So assuring that the generic type implements the Copy trait we could provide a map method that accepts &self instead of self.
I know that is a quite pointless observation, but I just want to understand better the language. :relaxed:

Thank you.


#4

Iterator::map passing the value by value is the most flexible thing it can do (in increasing power for the closure: by shared reference &T, by mutable reference &mut T, by value T).

If passing by reference is necessary, the iterator you’re calling map on will be yielding its elements behind an reference, e.g. if some_slice: &[T], then some_slice.iter() is an Iterator<Item = &T>, not Iterator<Item = T>, so the closure passed to map takes arguments of type &T in this case.