2016-11-17 4 views
1

Как получить доступ к вектору внутри опции без ржавчины, перемещая его при первом доступе?Как получить доступ к вектору несколько раз в пределах опции?

fn maybe_push(v_option: Option<&mut Vec<usize>>) -> usize { 
    let mut c = 0; 
    if let Some(v) = v_option { 
     for i in 0..10 { 
      v.push(i); 
      c += i; 
     } 
    } else { 
     for i in 0..10 { 
      c += i; 
     } 
    } 

    // second access, fails 
    if let Some(v) = v_option { 
     for i in 10..20 { 
      v.push(i); 
      c += i; 
     } 
    } else { 
     for i in 10..20 { 
      c += i; 
     } 
    } 

    return c; 
} 


fn main() { 
    let mut v: Vec<usize> = vec![]; 

    println!("{}", maybe_push(Some(&mut v))); 
    println!("{}", maybe_push(None)); 

    println!("{:?}", v); 
} 

Это дает ошибку:

error[E0382]: use of partially moved value: `v_option` 
    --> src/main.rs:16:22 
    | 
4 |  if let Some(v) = v_option { 
    |     - value moved here 
... 
16 |  if let Some(v) = v_option { 
    |      ^^^^^^^^ value used here after move 

Использование if let Some(ref mut v) = v_option {, который был предложен не может слишком:

error: cannot borrow immutable anonymous field `(v_option:std::prelude::v1::Some).0` as mutable 
--> src/main.rs:4:21 
    | 
4 |   if let Some(ref mut v) = v_option { 
    |      ^^^^^^^^^ 

error: cannot borrow immutable anonymous field `(v_option:std::prelude::v1::Some).0` as mutable 
    --> src/main.rs:17:21 
    | 
17 |   if let Some(ref mut v) = v_option { 
    |      ^^^^^^^^^ 

ответ

6

Matching значением движется значения в переменных шаблона. Перемещение делает исходное значение непригодным для использования, за исключением очень простых объектов, которые реализуют черту Copy, например числа. В отличие от указателей в C, изменяемые ссылки не являются копируемыми, которые можно увидеть в следующем примере, который не компилируется либо:

let mut v = vec![1, 2, 3]; 
let rv = &mut v; // mutable reference to v 
{ 
    // move rv to r1, r1 now becomes the sole mutable reference to v 
    let r1 = rv; 
    r1.push(4); 
} 
{ 
    let r2 = rv; // error: rv was already moved to r1 
    r2.push(5); 
} 

Ржавчина отклоняет выше, потому что он усиливает общее правило, запрещающее множество изменяемых ссылок на объект , Несмотря на то, что этот конкретный фрагмент является безопасным, одновременное использование нескольких изменяемых ссылок на один и тот же объект упростит запись типа небезопасных программ, которые Rust явно предназначен для предотвращения, например, те, которые содержат расы данных в многопоточном коде или те, которые обращаются к данным через недействительный итератор. Таким образом, назначение let r1 = rv может только переместитьrv ссылка на r1, запрещая инструкцию let r2 = rv, которая теперь относится к перемещенной переменной rv.

Чтобы исправить код, мы должны создать отдельные ссылки к ссылке. Это создаст две различных изменяемые ссылки на исходную ссылку, а не перемещение исходной изменяемую ссылки на внутреннюю сферу:

let mut v = vec![1, 2, 3]; 
let mut rv = &mut v; 
{ 
    // rr1 is a *new* mutable reference to rv - no move is performed 
    let rr1 = &mut rv; 
    rr1.push(4); 
} 
{ 
    // rr2 is a *separate* new mutable reference to rv - also no move 
    let rr2 = &mut rv; 
    rr2.push(5); 
} 

Синтаксис push вызова является то же самое с r1.push и rr1.push, потому что . оператор Русте автоматически разыменовать любого количество ссылок.

Для возврата к примеру от вопроса, ссылка на Vec<usize> в Option подобна v ссылки выше, и сопоставления его с помощью шаблона Some(v) перемещает ссылку в переменном с v узора.

Чтобы исправить это, шаблон должен быть изменен, как в приведенном выше примере, чтобы указать переменную, которая относится к к значению, которое само по себе является ссылкой. Это достигается с помощью синтаксиса if let Some(ref mut v). Как и выше, если объявление rv должно быть изменено на mutable, это исправление также требует изменения переменной Option. С учетом этих двух изменений, код компилируется:

fn maybe_push(mut v_option: Option<&mut Vec<usize>>) -> usize { 
    let mut c = 0; 
    if let Some(ref mut v) = v_option { 
     for i in 0..10 { 
      v.push(i); 
      c += i; 
     } 
    } else { 
     for i in 0..10 { 
      c += i; 
     } 
    } 

    if let Some(ref mut v) = v_option { 
     for i in 10..20 { 
      v.push(i); 
      c += i; 
     } 
    } else { 
     for i in 10..20 { 
      c += i; 
     } 
    } 

    return c; 
} 

fn main() { 
    let mut v: Vec<usize> = vec![]; 

    println!("{}", maybe_push(Some(&mut v))); 
    println!("{}", maybe_push(None)); 

    println!("{:?}", v); 
} 

Другая возможность заключается в том, чтобы использовать метод as_mut() вернуть содержимое опции как изменяемые ссылки на предыдущий контент. Это концептуально эквивалентно изменению от let r1 = rv до let rr1 = &mut rv в первом фрагменте и позволит использовать шаблон if let Some(v), где v все равно будет ссылкой на изменяемую ссылку на вектор. Это также требует, чтобы v_option был объявлен изменчивым.

+2

В книге: https://doc.rust-lang.org/book/patterns.html#ref-and-ref-mut (очень очень кратким). –

+1

@Shepmaster Хорошая альтернатива, спасибо. Я теперь обновил ответ, чтобы включить его, а также дополнительное объяснение, почему исходный ход не работает. – user4815162342