Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Calling a method on a value inside a mutable Option

Tags:

rust

I have a mutable Option type and I'm trying to mutate the thing inside the Some but I can't figure out how to do it.

use std::net::TcpStream;
use std::io::Write;

struct Foo {
    stream: Option<TcpStream>,
}

impl Foo {
    fn send(&mut self) {
        self.stream.map(|x| x.write(b"test")).expect("Couldn't write");
    }
}

This produces the error:

error[E0596]: cannot borrow immutable argument `x` as mutable
  --> src/main.rs:10:29
   |
10 |         self.stream.map(|x| x.write(b"test")).expect("Couldn't write");
   |                          -  ^ cannot borrow mutably
   |                          |
   |                          consider changing this to `mut x`

Can someone try to implement send as an example to help me understand?

like image 738
kelloti Avatar asked Dec 08 '14 15:12

kelloti


3 Answers

As Vladimir Matveev points out, if let is even nicer, and is more idiomatic than iterating over the Option:

#[derive(Debug)]
struct Foo {
    stream: Option<i32>,
}

impl Foo {
    fn send(&mut self) {
        if let Some(ref mut x) = self.stream {
            *x += 1;
        }
    }
}

fn main() {
    let mut f = Foo { stream: Some(0) };
    println!("{:?}", f);

    f.send();
    println!("{:?}", f);
}

As of Rust 1.26, match ergonomics allows you to omit some of the keywords:

impl Foo {
    fn send(&mut self) {
        if let Some(x) = &mut self.stream {
            *x += 1;
        }
    }
}

Before that, I would usually use Option::as_mut:

impl Foo {
    fn send(&mut self) {
        if let Some(x) = self.stream.as_mut() {
            *x += 1;
        }
    }
}

Other options

As Vladimir Matveev points out (again!), map is usually used to transform data, not for side effects (which I agree with). You could instead use iter_mut (or the shorthand of &mut collection), as I feel that iteration is usually for side effects. I like this because it means our code can avoid having a conditional:

impl Foo {
    fn send(&mut self) {
        for x in &mut self.stream {
            *x += 1;
        }
    }
}

You can also leverage the IntoIterator implementation for Option:

impl Foo {
    fn send(&mut self) {
        for x in self.stream.as_mut() {
            *x += 1;
        }
    }
}
like image 50
Shepmaster Avatar answered Nov 17 '22 00:11

Shepmaster


As a follow-up to @idupree's variant, it is also possible to use if-let syntax:

struct Foo {
    stream: Option<i32>,
}

impl Foo {
    fn send(&mut self) {
        if let Some(ref mut x) = self.stream {
            *x = 0;
        }
    }
}

I'd also argue that this is more idiomatic than map(), because map() method is intended for transforming an Option, not executing side effects (and assignment is a side effect).

like image 25
Vladimir Matveev Avatar answered Nov 17 '22 00:11

Vladimir Matveev


You can match on the Option directly, like the following (showing i32 rather than TcpStream):

struct Foo {
    stream: Option<i32>,
}

impl Foo {
    fn send(&mut self) {
        match self.stream {
            Some(ref mut x) => {
                *x = 0;
            }
            None => {}
        }
    }
}

(Not sure whether that's the most idiomatic way to do it.)

like image 4
idupree Avatar answered Nov 16 '22 23:11

idupree