30

In order to get to know Rust a bit better, I am building a simple text editor and have the following structs:

struct File {
    rows: Vec<Row>,
    filename: Option<String>
}

impl File {
    fn row(&self, index: u16) -> &Row{
        &self.rows[index as usize]
    }

}

struct Row {
    string: String,
}

struct EditorState {
    file: File,
}

As you can see, I am keeping the state of an editor in a struct, which references the file, which contains a number of rows, which contains a string (Each of these structs has more fields, but I have removed the ones not relevant to the problem)

Now I want to make my rows editable and added this:

impl Row {
    fn insert(&mut self, at: u16, c: char) {
        let at = at as usize;
        if at >= self.string.len() {
            self.string.push(c);
        } else {
            self.string.insert(at, c)
        }
    }
}

This is how I try to update the row:

//In the actual functon, I am capturing the keypress,
//get the correct row from the state and pass it and the pressed
// char to row.insert
fn update_row(mut state: &mut EditorState)  {
let row = &state.file.row(0);
row.insert(0, 'a');

}

This fails to compile:

error[E0596]: cannot borrow `*row` as mutable, as it is behind a `&` reference

From the error, I can see that the issue is that Row should be mutable so I can edit it (which makes sense, since I am mutating it's String). I can't figure out a) how to be able to mutate the string here, and b) how to do this without having row always return a mutable reference, as in all other cases, I am calling row to read a row, and not to write it.

Philipp
  • 1,903
  • 2
  • 24
  • 33
  • 1
    @SvenMarnach That would give you an `&mut &Row`, which doesn't really help. – fjh Aug 09 '19 at 21:33
  • 5
    No, strike that. The `row()` function only returns a read-only reference, so you can't use that reference to modify the row. You need to add a `row_mut()` that returns a mutable reference. – Sven Marnach Aug 09 '19 at 21:33

3 Answers3

8

Here is a more idiomatic implementation for File:

impl File {
    fn row(&self, index: usize) -> Option<&Row> {
        self.rows.get(index)
    }

    fn row_mut(&mut self, index: usize) -> Option<&mut Row> {
        self.rows.get_mut(index)
    }
}

Items of note here:

  • Your implementation would panic if index is out of bounds. The idiomatic way of handling this is to return an Option, which get and get_mut allow you to get for free.
  • Using u16 does not make much sense, as Vec is indexed using usize. Using u16 is arbitrary here unless you really want to provide hard-coded limitations. In that case, I wouldn't rely on the type's max value but a constant instead that would make the intent clearer.
SirDarius
  • 41,440
  • 8
  • 86
  • 100
  • Thanks for a) answering the question, b) pointing me towards `get` and `get_mut`, and c) highlighting the `u16` issue, which really does not make a lot of sense. – Philipp Aug 10 '19 at 05:32
4

What you want is impossible. You'll have to write two functions (note that I replaced u16 with usize - there is no reason why you should limit yourself to 65536 characters per line):

fn row(&self, index: usize) -> &Row {
    &self.rows[index]
}

fn row_mut(&mut self, index: usize) -> &mut Row {
    &mut self.rows[index]
}

Note that this is a common pattern across all Rust code. For example Vec has get(idx) and get_mut(idx).

orlp
  • 112,504
  • 36
  • 218
  • 315
0

File encapsulates row, so don't expose the row.
Instead implement methods in File to do something on the encapsulated row.

generally speaking, programs should have structure like

object.do_something() 

in your case:

file.do_something_with_row(x)  

instead of

let mut row = file.row_mut()
// do something with row here, rows leaking all over the place.
Lukas
  • 391
  • 4
  • 11