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

#vector #rust

Вопрос:

Каков хороший способ инициализации вектора без mut ключевого слова, учитывая, что вектор будет неизменным после инициализации?

Например:

 // nums is a `i32` vector(Vec<i32>)
// here is to pad two 1s with both ends
let mut padded: Vec<i32> = vec![0; len_p];
padded[0] = 1;
padded[len_n   1] = 1;
for (idx, amp;num) in nums.iter().enumerate(){
    padded[idx   1] = num;
}
// `padded` will be read-only/immutable ever since
 

В противном случае, чтобы отметить padded mut , просто инициализировать его, мне кажется пустой тратой неизменности, так как я не могу применить его после инициализации.

Ответ №1:

Распространенная идиома, встречающаяся в Rust, заключается в том, чтобы ввести локальную область действия именно для этой цели.

 let padded: Vec<i32> = {
  let mut tmp: Vec<i32> = vec![0; len_p];
  tmp[0] = 1;
  tmp[len_n   1] = 1;
  for (idx, amp;num) in nums.iter().enumerate(){
    tmp[idx   1] = num;
  }
  tmp
};
 

Внутри вложенной области у нас есть локальная переменная tmp , которая называется изменяемой. Затем, когда мы доберемся до конца этой области, мы передадим право собственности на этот вектор неизменяемой переменной padded . Компилятор может (и, скорее всего, будет) оптимизировать любое фактическое движение, которое происходит, и это скомпилируется до чего-то столь же эффективного, как то, что вы написали. Но с точки зрения проверки заимствований, tmp она изменчива и padded неизменна, как и требуется. Как только tmp выходит за рамки, больше нет возможности изменять padded .

Помните, что владелец значения всегда определяет, является ли это значение изменяемым или нет, поэтому, когда вы передаете право собственности кому-то другому, он может свободно изменять изменяемость значения при вступлении во владение.

Комментарии:

1. Если padded предполагается, что он остается неизменным навсегда (т. Е. Не перемещается, а затем мутирует в другом месте), также может быть хорошей идеей сделать его типом Box<[i32]> , вызвав tmp.into_boxed_slice() в конце области.

Ответ №2:

Есть 3 способа:

  1. Наиболее функциональный (FP) подход: Используйте сбор
 let v: Vec<i32> = std::iter::once(1).chain(
      nums.iter().copied()
   )
   .collect();
 
  1. Инициализировать в области действия. Полезно, когда вам нужны некоторые дополнительные переменные, которые будут очищены после этого.
 let v = {
  let mut v: Vec<i32> = vec![0; len_p];
  v[0] = 1;
  v[len_n   1] = 1;
  for (amp;src, dst) in nums.iter().zip(v[1..].iter_mut()){
     *dst = src;
  }
  v
}
 
  1. Свяжите имя заново и переместите в него вектор. Полезно, когда инициализация ясна и проста.
   let mut v: Vec<i32> = vec![0; len_p];
  v[0] = 1;
  v[len_n   1] = 1;
  for (amp;src, dst) in nums.iter().zip(v[1..].iter_mut()){
     *dst = src;
  }
  // Rebind to immutable variable
  let v = v;
  // Cannot mutate here