Разработайте трейт в Rust с изменяемым геттером и неизменяемым геттером, который реализует неизменный геттер по умолчанию

Я хочу разработать черту с изменяемым и неизменяемым геттером для некоторого поля self. Однако я хочу, чтобы разработчик трейта реализовал только один метод, как правило, изменяемый геттер.

Я нашел это решение (детская площадка):

pub trait MyTrait<T> {
    fn inside_mut(&mut self) -> &mut T; 
    
    fn inside_ref(&self) -> &T {
        let ptr = self as *const Self as *mut Self;
        &* unsafe { &mut *ptr }.inside_mut()
    }
}

pub struct Data(String);
impl MyTrait<String> for Data {
    fn inside_mut(&mut self) -> &mut String { &mut self.0 }
}

fn main() {
    let mut data = Data(format!("Foo"));
    let x: &String = data.inside_ref();
    let y: &String = data.inside_ref();
    println!("x -> {x}");
    println!("y -> {y}");
    *data.inside_mut() = format!("Bar");
    println!("data.0 -> {}", data.0);
}

Результат:

Standard Error

   Compiling playground v0.0.1 (/playground)
    Finished release [optimized] target(s) in 0.93s
     Running `target/release/playground`

Standard Output

x -> Foo
y -> Foo
data.0 -> Bar

Мой вопрос: этот подход звучит?

Не эксперт по небезопасной ржавчине, но нет, это не кажется безопасным, поскольку мири обнаруживает UB. Вы можете запустить Мири на детской площадке. Вы можете найти эту опцию в меню инструментов в правом верхнем углу.

Jonas Fassbender 05.04.2023 12:23

Техническое замечание: Вы, наверное, спрашиваете, является ли такой подход разумным, небезопасным.

Finomnis 05.04.2023 12:45

Точно! Я отредактировал вопрос.

FreD 05.04.2023 12:46
Почему Python в конце концов умрет
Почему Python в конце концов умрет
Последние 20 лет были действительно хорошими для Python. Он прошел путь от "просто языка сценариев" до основного языка, используемого для написания...
1
3
75
2
Перейти к ответу Данный вопрос помечен как решенный

Ответы 2

Ответ принят как подходящий

Нет, это не звук.

Пользователь может изменить self в своей реализации inside_mut, что также изменит структуру в inside_ref.

Так:

pub trait MyTrait<T> {
    fn inside_mut(&mut self) -> &mut T;

    fn inside_ref(&self) -> &T {
        let ptr = self as *const Self as *mut Self;
        &*unsafe { (&mut *ptr).inside_mut() }
    }
}

#[derive(Debug)]
struct MyStruct {
    value: i32,
}

impl MyTrait<i32> for MyStruct {
    fn inside_mut(&mut self) -> &mut i32 {
        self.value += 1;
        &mut self.value
    }
}

fn main() {
    // Important: this is not `mut`!!!
    let x = MyStruct { value: 42 };

    println!("{:?}", x);
    x.inside_ref();
    println!("{:?}", x);
}
MyStruct { value: 42 }
MyStruct { value: 43 }

Это означает, что эта реализация несостоятельна:

Поведение считается неопределенным

  • Изменение неизменяемых данных. Все данные внутри элемента const неизменяемы. Более того, все данные, полученные через общую ссылку или данные, принадлежащие неизменяемой привязке, являются неизменяемыми, если только эти данные не содержатся в UnsafeCell<U>.

О, да! Я не заметил этой тонкости!

FreD 05.04.2023 12:45

Даже если пользователь этого не делает, это немедленный UB, поскольку преобразование общей ссылки в изменяемую ссылку является немедленным UB, даже если она не изменена.

Chayim Friedman 05.04.2023 13:39

Посмотрев на ответ Finomnis, я пришел к этому решению (площадка), для которого miri не выдает предупреждение UB:

use core::cell::UnsafeCell;
pub trait MyTrait<T> {
    fn inside_mut(&mut self) -> &mut T {
        let ptr = self.implement_ref().get();
        unsafe { &mut *ptr }
    }

    fn inside_ref(&self) -> &T {
        let ptr = self.implement_ref().get();
        unsafe { & *ptr }
    }

    fn implement_ref(&self) -> &UnsafeCell<T>;
}

pub struct Data(UnsafeCell<String>);

impl Data {
    pub fn new(s:String) -> Self {
        Data(UnsafeCell::new(s))
    }    
}

impl MyTrait<String> for Data {
    fn implement_ref(&self) -> &UnsafeCell<String> { &self.0 }
}

fn main() {
    let mut data = Data::new(format!("Foo"));
    let x: &String = data.inside_ref();
    let y: &String = data.inside_ref();
    println!("x -> {x}");
    println!("y -> {y}");
    *data.inside_mut() = format!("Bar");
    println!("data.0 -> {}", data.inside_ref());
}

Тем не менее, у меня все еще есть вопрос. Мири выдает UB-алерт для следующего кода (площадка):

pub trait MyTrait<T> {
    fn inside_ref(&self) -> &T;
    
    fn inside_mut(&mut self) -> &mut T  {
        let ptr = self.inside_ref() as *const T as *mut T;
        unsafe { &mut *ptr }
    }
}

pub struct Data(String);
impl MyTrait<String> for Data {
    fn inside_ref(&self) -> &String { &self.0 }
}

fn main() {
    let mut data = Data(format!("Foo"));
    let x: &String = data.inside_ref();
    let y: &String = data.inside_ref();
    println!("x -> {x}");
    println!("y -> {y}");
    *data.inside_mut() = format!("Bar");
    println!("data.0 -> {}", data.0);
}

Но хороший контрпример, представленный Finomnis, в этом случае не работает. Может ли кто-нибудь привести иллюстрацию несостоятельности последнего случая?


Обновлено: вдохновлено контрпримером cafce25 (детская площадка):

static IMMUTABLE_STATIC_STR: &'static str = "IMMUTABLE FOO";

pub trait MyTrait<T> {
    fn inside_ref(&self) -> &T;
    
    fn inside_mut(&mut self) -> &mut T  {
        let ptr = self.inside_ref() as *const T as *mut T;
        unsafe { &mut *ptr }
    }
}

pub struct Data;
impl MyTrait<&'static str> for Data {
    fn inside_ref(&self) -> &&'static str { &IMMUTABLE_STATIC_STR }
}

fn main() {
    let mut data = Data;
    let x: &&'static str = data.inside_ref();
    let y: &&'static str = data.inside_ref();
    println!("x -> {x}");
    println!("y -> {y}");
    *data.inside_mut() = "Bar";
    println!("data.inside_ref() -> {}", data.inside_ref());
}

Результат:

Standard Error

   Compiling playground v0.0.1 (/playground)
    Finished release [optimized] target(s) in 1.61s
     Running `target/release/playground`

Standard Output

x -> IMMUTABLE FOO
y -> IMMUTABLE FOO
data.inside_ref() -> IMMUTABLE FOO

Во-первых, для нового вопроса, пожалуйста, задайте новый вопрос SO. Во-вторых, UnsafeCell работает, но это плохая идея. Гораздо лучше иметь два метода для реализации.

Chayim Friedman 05.04.2023 14:12

Мири не проверяет (и не может) исправность. Он только проверяет, содержит ли одно выполнение вашей программы UB.

Chayim Friedman 05.04.2023 14:13

Все в порядке! Я не думаю, что новый вопрос стоит того. И спасибо за ваш совет!

FreD 05.04.2023 14:36

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

FreD 05.04.2023 17:01
fn inside_ref(&self) -> &T { &IMMUTABLE_STATIC_T } угу.
cafce25 05.04.2023 17:11

Большой! Мне еще многому нужно научиться, прежде чем я смогу с такой легкостью приводить контрпримеры.

FreD 06.04.2023 08:55

@ChayimFriedman Я не понимаю, почему пример так вас раздражает, ОП не понимал, почему это плохая идея. Пример говорит о вас более чем на 1000 слов, что это немедленный UB

cafce25 10.04.2023 08:42

@ cafce25 Это плохая идея, потому что это UB во всех случаях и примерах.

Chayim Friedman 10.04.2023 09:06

@ChayimFriedman Да, конечно, я не понимаю, почему пример, помогающий понять ОП, - это плохо, о чем нужно разглагольствовать.

cafce25 10.04.2023 09:11

@ cafce25 Это может заставить ОП (и я думаю, что это заставило их) думать, что только когда мы находим контрпример, это плохо.

Chayim Friedman 10.04.2023 09:13

@ChayimFriedman Я задал этот вопрос, потому что знал, что это решение с изменяемым преобразованием ссылок было неправильным, и я хотел знать, как избежать UB. Что касается меня, то мне не нужен контрпример, чтобы убедить меня не использовать УБ. Однако мне придется познакомить с ржавчиной некоторых программистов на C/C++, и в этом контексте мне будут полезны контрпримеры. У меня уже есть контрпримеры UB, подразумеваемые оптимизацией компилятора. Но примеры в этом посте другие, потому что они являются следствием языка. Для меня очень интересно это увидеть.

FreD 10.04.2023 18:40

Другие вопросы по теме