2017-01-03 9 views
14

Несколько раз, я столкнулся с сценарием, где необходим метод доступа для обеих изменяемых и неизменяемых ссылок.Как избежать дублирования функций доступа для изменяемых и неизменных ссылок в Rust?

Для ~ 3 строк не проблема дублировать логику, но когда логика становится более сложной, неплохо скопировать большие блоки кода.

Я хотел бы иметь возможность повторно использовать код для обоих.

Предоставляет ли Rust некоторый способ справиться с этим лучше, чем скопировать код или использовать unsafe?

.: например

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; 
    } 
} 

Вот более подробный отрывок кода базы, где неизменный обратный аргумент был приведен к изменяемым для поддержки как неизменные и изменяемые версии функции. Это использует тип обернутого указателя (ConstP и MutP для неизменяемых и изменяемых ссылок), но логика функции должна быть ясной.

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() 
    }; 
} 
+0

Чтобы уточнить, 'MyStruct' как-то сродни карте, а 20 строк кода, которые вы используете, используются для получения ссылки на' Bar'?Это может быть, потому что это утро, но у меня есть некоторые трудности с тем, как именно это происходит, поэтому трудно оценить потенциальные ответы, которые я придумал ... не могли бы вы придумать MCVE? –

+0

Добавлен пример функции, где мне это нужно. Выложил бы что-то более простое и самодостаточное - но более упрощая проблему * может * использовать некоторую особенность Rust, которая не может использоваться в более вовлеченных случаях. – ideasman42

+1

Теперь с вашим примером это яснее, спасибо. У меня были те же проблемы на C++ и регулярно использовались 'const_cast' аналогично вашему примеру; Мне интересно узнать, что люди смогут сделать, может быть, что-то с чертами (со связанными типами) доступно для абстрактной над изменчивостью. –

ответ

7

У вас нет, действительно. Напомним, что T, &T и &mut T все разных типов. В этом контексте ваш вопрос совпадает с вопросом «Как избежать написания дублирующих функций доступа для String и HashMap».

Матье M были правильные термины "абстрактные над изменчивостью":

TL, DR это то, что Rust, вероятно, необходимо будет улучшить с помощью новых функций для поддержки этого. Поскольку никто не преуспел, никто не на 100% уверен, какие функции должны быть. Текущая лучшая догадка более высокие типы типов (HKT).

-3

В настоящее время Rust не поддерживает абстрагирование над изменчивостью.

Есть несколько способов это может быть достигнуто, хотя они не идеальны:

  • Используйте макрос для расширения повторяющегося кода, объявить макрос и поделена между обеими функциями - должно быть построено так оно Разумеется, работает для изменчивых и неизменных.
  • Напишите неизменяемую версию функции (чтобы ничего не изменилось), затем напишите функцию обертки для изменяемой версии, которая выполняет результат unsafe на результат, чтобы сделать его изменяемым.

Ни один из них является очень привлекательным (макрос является чрезмерно многословным и немного менее читабельным, добавляют некоторый код-раздувание), то unsafe является более удобным для чтения, но было бы неплохо, чтобы избежать, так как литья из непреложных изменяемых не так хорошо иметь через кодовую базу.

На данный момент лучший вариант, насколько я могу видеть (где код копирования не подходит), заключается в том, чтобы написать неизменяемую версию функции, а затем обернуть ее с помощью функции mut, в которой оба входа и выходы изменяемы.

Для этого требуется вывод unsafe на выходе функции, поэтому он не идеален.


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

+1

-1, поскольку создание поддельной ссылки '& mut' - UB; программе становится трудно рассуждать, и вы рискуете потерять основное преимущество Rust: ваша программа безопасна для памяти, если она компилирует – bluss

+2

. Исключение критических инвариантов при использовании блоков «небезопасных» отсутствует. Помните, что небезопасный код не нарушает инварианты Руста, он предназначен для поддержания их вручную. * *. – bluss

+0

@ Ответ Shepmaster утверждает, что это не поддерживается, это нормально, но есть способы сделать это, как включено в этот ответ. Таким образом, даже если его downvoted как плохой выбор - его стоит отметить, что это можно сделать и * может быть лучше, чем копировать вставки больших блоков кода. – ideasman42

3

(площадка ссылки на решения с использованием type parameters и associated types)

В этом случае &T и &mut T просто два разных типа. Код, который является общим для разных типов (как во время компиляции, так и во время выполнения), идиоматически написан в Rust, используя черты. Например, учитывая:

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

предположит, что мы хотим, чтобы обеспечить Bar с общим аксессором для его члена Foo данных. Аксессуар должен работать как на &Bar, так и на &mut Bar, соответственно возвращая &Foo или &mut Foo. Таким образом, мы напишем черта FooGetter

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

, чья работа состоит в том, чтобы быть универсальным по конкретному типу Bar мы имеем. Его размер Output будет зависеть от Bar, так как мы хотим get иногда возвращаться &Foo, а иногда и &mut Foo. Обратите также внимание на то, что он потребляет self типа Self. Так как мы хотим get порождающим над &Bar и &mut Bar нам необходимо реализовать FooGetter для обоих, так что Self имеет соответствующие типы:

// 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 } 
} 

Теперь мы можем легко использовать .get() в общий код для получения & или &mut ссылки Foo от &Bar или &mut Bar (всего лишь требуя T: FooGetter). Например:

// 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); 
} 

Обратите внимание, что вы также можете реализовать FooGetter для Bar, так что get является общим над &T, &mut T и сам T (перемещая его в). На самом деле, как метод .iter() реализован в стандартной библиотеке и почему он всегда делает «правильную вещь» независимо от ссылочной аргументации, на которую он ссылается.

+0

Кажется, что это работает только для методов? (которые берут себя), хотя я полагаю, что функции могут быть обернуты и вызвать методы. – ideasman42

+0

@ ideasman42 Да, см. Функцию foo в последнем фрагменте кода выше, как вы можете обернуть вызов метода в свободной функции. Вы можете применить любой метод метода/метода объекта в свободной функции. Обратите внимание, что мы должны использовать методы, потому что для вас требуются «перегрузка функций» (как в разных функциях '.get', вызываемых в зависимости от типов аргументов), которые недоступны для бесплатных функций. В частности, это не ограничивается типом первого аргумента, поскольку вы можете использовать связанные типы для перегрузки типов других аргументов, а также в ночной специализации. – gnzlbg