what used the tag for code are and all reference rust immutability

reference - used - ¿Cómo evitar escribir funciones de acceso duplicadas para referencias mutables e inmutables en Rust?



tag use in html (3)

Algunas veces, me he encontrado con el escenario en el que se necesita un método de acceso para referencias mutables e inmutables.

Para ~ 3 líneas no es un problema duplicar la lógica, pero cuando la lógica se vuelve más compleja, no es bueno copiar y pegar grandes bloques de código.

Me gustaría poder reutilizar el código para ambos.

¿Rust proporciona alguna manera de manejar esto mejor que copiar y pegar código, o usar moldes unsafe ?

p.ej:

impl MyStruct { pub fn get_foo(&self) -> &Bar { // ~20 lines of code // --- snip --- return bar; } pub fn get_foo_mut(&mut self) -> &mut Bar { // ~20 lines of code // (exactly matching previous code except `bar` is mutable) // --- snip --- return bar; } }

Aquí hay un extracto más detallado de una base de código donde un argumento de retorno inmutable se convirtió en mutable para admitir las versiones inmutable y mutable de una función. Esto utiliza un tipo de puntero envuelto ( ConstP y MutP para referencias inmutables y mutables), pero la lógica de la función debe ser clara.

pub fn face_vert_share_loop<V, F>(f: F, v: V) -> LoopConstP where V: Into<VertConstP>, F: Into<FaceConstP> { into_expand!(f, v); let l_first = f.l_first.as_const(); let mut l_iter = l_first; loop { if l_iter.v == v { return l_iter; } l_iter = l_iter.next.as_const(); if l_iter == l_first { break; } } return null_const(); } pub fn face_vert_share_loop_mut(f: FaceMutP, v: VertMutP) -> LoopMutP { let l = face_vert_share_loop(f, v); return unsafe { // Evil! but what are the alternatives? // Perform an unsafe `const` to `mut` cast :( // While in general this should be avoided, // its ''OK'' in this case since input is also mutable. l.as_mut() }; }


(enlaces de juegos a soluciones usando parámetros de tipo y tipos asociados )

En este caso, &T y &mut T son solo dos tipos diferentes. El código que es genérico sobre diferentes tipos (tanto en tiempo de compilación como en tiempo de ejecución) se escribe idiomáticamente en Rust utilizando rasgos. Por ejemplo, dado:

struct Foo { value: i32 } struct Bar { foo: Foo }

supongamos que queremos proporcionarle a Bar un acceso genérico para su miembro de datos Foo . El descriptor de acceso debe trabajar tanto en &Bar como en &mut Bar devolviendo adecuadamente &Foo o &mut Foo . Entonces escribimos un rasgo FooGetter

trait FooGetter { type Output; fn get(self) -> Self::Output; }

cuyo trabajo es ser genérico sobre el tipo particular de Bar que tenemos. Su tipo de Output dependerá de la Bar ya que queremos get a veces return &Foo y a veces &mut Foo . Tenga en cuenta también que consume self del tipo Self . Como queremos get a ser genéricos sobre &Bar y &mut Bar , necesitamos implementar FooGetter para ambos, de modo que Self tenga los tipos apropiados:

// FooGetter::Self == &Bar impl<''a> FooGetter for &''a Bar { type Output = &''a Foo; fn get(self) -> Self::Output { & self.foo } } // FooGetter::Self == &mut Bar impl<''a> FooGetter for &''a mut Bar { type Output = &''a mut Foo; fn get(mut self) -> Self::Output { &mut self.foo } }

Ahora podemos usar fácilmente .get() en código genérico para obtener referencias de & o &mut a Foo desde una &Bar o una &mut Bar (simplemente requiriendo T: FooGetter ). Por ejemplo:

// exemplary generic function: fn foo<T: FooGetter>(t: T) -> <T as FooGetter>::Output { t.get() } fn main() { let x = Bar { foo: Foo {value: 2} }; let mut y = Bar { foo: Foo {value: 2} }; foo(&mut y).value = 3; println!("{} {}/n", foo(&x).value, foo(&mut y).value); }

Tenga en cuenta que también puede implementar FooGetter para Bar , de modo que get sea ​​genérico sobre &T , &mut T y T (moviéndolo). Así es como se implementa el método .iter() en la biblioteca estándar, y por qué siempre hace "lo correcto" independientemente de la referencia del argumento en el que se invoca.


Actualmente, Rust no admite abstracción sobre mutabilidad.

Hay algunas formas en que esto se puede lograr, aunque no son ideales:

  • Use una macro para expandir el código duplicado, declare la macro y comparta entre ambas funciones; debe construirse de modo que funcione para mutable e inmutable, por supuesto.
  • Escriba la versión inmutable de la función (para asegurarse de que no se cambie nada), luego escriba una función de contenedor para la versión mutable que realice una unsafe en el resultado para hacerlo mutable.

Ninguno de estos es muy atractivo (una macro es demasiado detallada y un poco menos legible, agrega un poco de código hinchado), lo unsafe es más legible, pero sería bueno evitarlo, ya que la conversión de inmutable a mutable no es tan agradable tener a través de una base de código.

Por ahora, la mejor opción hasta donde puedo ver (donde el código de copiar y pegar no es aceptable), es escribir una versión inmutable de la función, luego envolverla con una versión mut de la función donde las entradas y salidas son mutables .

Esto requiere una unsafe en la salida de la función, por lo que no es ideal.

Nota: es importante que la función inmutable contenga el cuerpo del código, ya que lo contrario permitirá la mutación accidental de lo que podría ser una entrada inmutable.


No lo haces, de verdad. Recuerde que T , &T y &mut T son tipos diferentes . En ese contexto, su pregunta es la misma que preguntar "Cómo evitar escribir funciones de acceso duplicadas para String y HashMap ".

Matthieu M tenía los términos correctos "resumen sobre la mutabilidad":

El TL; DR es que Rust probablemente necesitaría ser mejorado con nuevas características para soportar esto. Como nadie ha tenido éxito, nadie está 100% seguro de qué características necesitarían tener. La mejor suposición actual es los tipos más altos (HKT).