2017-01-12 8 views
2

Я пытаюсь определить черту с методом, возвращающим некоторые данные, но эти данные не должны быть изменены impl, т. Е. Его можно установить только один раз и оставаться в этом значении для времени жизни impl. Есть ли способ обеспечить это?Необязательные данные по признаку

Вот как я бы сделать это в C#, для справки:

public abstract class Foo 
{ 
    private readonly uint number; 

    public Foo(uint number) { this.number = numbers; } 

    public uint GetNumber() { return number; } 

} 
+5

У меня нет полного ответа для вас, поэтому я просто хочу оставить комментарий. 1) Более близкая аналогия для признака в C# будет интерфейсом. Как вы подходите к решению этого с помощью интерфейса? 2) Можете ли вы объяснить на более высоком уровне, какую проблему вы пытаетесь решить? Почему реализация этой черты должна содержать кусок неизменяемых данных? Почему он должен быть неизменным? –

+2

Похож на проблему XY. Что именно вы пытаетесь сделать? – coredump

ответ

2

Короткий ответ на ваш вопрос не. Невозможно выполнить это способом, аналогичным подходу C#. К счастью, Rust обеспечивает лучший контроль над изменчивостью, чем C#.

Понимание того, как неизменяемость работает в Rust и как она отличается от таких языков, как C# и Java, важна.

Неизменность в C#

class Foo { 
    readonly Bar bar = new Bar(); 
    uint lives; 
} 

Некоторые вещи, чтобы отметить:

  • Неизменность определяется в полевых условиях.
  • Невосприимчивость неглубокая. Например, хотя ссылка на bar неизменна, значения, которые bar ссылаются, все еще изменяемы.
  • Неизменность в C# легко сворачивается отражением. Есть edge cases, где он может быть искажен даже без отражения.

Неизменность в Русте

struct Foo { 
    bar: Bar, 
    lives: u32 
} 

Первое, что нужно отметить, что определение структуры ничего не говорит о неизменности своих полей. Это потому, что в ржавчине нет такой вещи, как изменчивость на уровне поля. Изменчивость в Русте определяется на связывания до значения:

// Declare an immutable binding to a Foo 
let foo = Foo { bar: Bar::new(), lives: 10 }; 

// Attempting to mutate the value that foo points to is a compile error 
foo.lives = 5; // compile error! 

foo.bar.baz = 6; // Also a compile error, foo is deeply immutable 

// We can redefine the binding to be mutable 
let foo = mut foo; // foo is now mutable! 

foo.lives = 5; // mutating foo here would be valid 
foo.bar.baz = 6; // this is also valid, foo is deeply mutable 

Как вы можете видеть, переменчивость в Русте проще и менее нюансы, чем в C#: Это привязка к значению, которое определяет, будет ли это изменчиво, и он либо глубоко изменчив, либо глубоко непреложен. *.

Со всем этим, давайте попробуем смоделировать вашу проблему в Rust.

Во-первых, мы определим черта с эквивалентным GetNumber() способом:

trait Bar { 
    fn number(&self) -> u32; 
} 

Поскольку number() принимает непреложный привязку к self, любой тип, реализующий Bar не сможет мутировать себя через вызов к number():

struct Foo { 
    number: u32, 
    oranges: u32 
} 

impl Bar for Foo { 
    fn number(&self) -> u32 { 
     self.number += 1; // Compile error. We have an immutable binding to self 
     self.number 
    } 
} 

как вы можете видеть, контролируя переменчивость в Русте, все о контроле, как определяются привязки.

Введем метода для нашего признака, который определяет изменяемое связывание с self и обновить нашу реализацию на Foo:

trait Bar { 
    fn number(&self) -> u32; 
    fn inc_oranges(&mut self); 
} 

impl Bar for Foo { 
    fn number(&self) -> u32 { 
     self.number 
    } 

    fn inc_oranges(&mut self) { 
     // We have a mutable reference to self. We can mutate any part of self: 
     self.oranges += 1; 
     self.number += 1; // We can *also* mutate number 
    } 
} 

Это где вы могли бы начать в пользу C# подхода: В C#, вы можете объявить number как поле readonly, оставляя oranges изменчивым, но в Rust, если признак объявляет изменяемое связывание с self, любая часть self может быть мутирована. К счастью, есть способ обойти это.

*интерьер мутабильности

Ржавчина обеспечивает способ мутировать значения, которые являются частью непреложным связывания с путем cell модуля. Короче говоря, эти типы допускают мутацию, сохраняя при этом гарантии, обеспечиваемые использованием неизменяемых привязок. Он делает это, перемещая то, что в противном случае было бы проверкой времени выполнения с компиляцией (нулевой стоимостью) на проверку времени выполнения.

Давайте соберем все вместе сейчас:

struct Foo { 
    number: u32, 
    orange: Cell(u32) // allow mutation via an immutable binding 
} 

trait Bar { 
    fn number(&self) -> u32; 
    fn inc_oranges(&self); // self is now an immutable binding 
} 

impl Bar for Foo { 
    fn number(&self) -> u32 { 
     self.number 
    } 

    fn inc_oranges(&self) { 

     // We can mutate oranges via cell functions even though self is immutable 
     let cur_oranges = self.oranges.get(); 
     self.oranges.set(cur_oranges + 1); 

     self.number += 1; // This would be a compile error 
    } 
} 

Таким образом, мы можем эффективно достичь эквивалент вашей C, например # путем:

  • Определение неизменяемых привязок self на impls
  • Использование типов ячеек для обеспечения внутренней изменчивости на неизменяемых связываниях

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