замените значение за изменяемой ссылкой, переместив и сопоставив исходное

#rust

Вопрос:

TLDR: Я хочу заменить T заднюю amp;mut T часть новой T , которую я создаю из старой T

Примечание: пожалуйста, простите меня, если решение этой проблемы легко найти. Я много гуглил, но не уверен, как правильно сформулировать проблему.

Пример кода (игровая площадка):

 struct T { s: String }

fn main() {
    let ref mut t = T { s: "hello".to_string() };
    
    *t = T {
        s: t.s   " world"
    }
}
 

Это, очевидно , не удается, потому String что надстройка выполняется self по значению и, следовательно, потребует возможности выхода T , что, однако, невозможно, поскольку T находится за ссылкой.

Из того, что я смог найти, обычный способ добиться этого-сделать что-то вроде

 let old_t = std::mem::replace(t, T { s: Default::default() });

t.s = old_t   " world";
 

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

К счастью, в моем случае использования я могу создать заполнитель T , но мне все еще непонятно, почему api, подобный этому, невозможен:

 map_in_place(t, |old_t: T| T { s: old_t.s   " world" });
 

Есть ли причина, по которой это невозможно или обычно не делается?

Ответ №1:

Есть ли причина, по которой [ map_in_place ] невозможно или обычно не выполняется?

А map_in_place действительно возможно:

 // XXX unsound, don't use
fn map_in_place<T>(t_ref: amp;mut T, f: impl FnOnce(T) -> T) {
    let t_ptr = t_ref as *mut T;
    drop(t_ref); // avoid UB due to dangling ref
    unsafe {
        let t = std::ptr::read(t_ptr);
        let new_t = f(t);
        std::ptr::write(t_ptr, new_t);
    }
}
 

Но, к сожалению, это не звучит. Если f() запаникует, *t_ref будет сброшен дважды. Сначала он будет отброшен при развертывании области действия f() , которая считает, что ей принадлежит полученное значение. Затем он будет удален во второй раз владельцем значения t_ref , у которого он заимствован, который так и не получил напоминания о том, что значение, которым, по его мнению, оно владеет, на самом деле является мусором, потому что оно уже было удалено. Это можно даже воспроизвести на игровой площадке, где простое panic!() закрытие приводит к двойному бесплатному.

По этой причине реализация map_in_place самой по себе должна быть отмечена как небезопасная, с договором о безопасности, который f() не вызывает паники. Но поскольку практически все в Rust может вызвать панику (например, любой доступ к срезу), было бы трудно гарантировать, что контракт на безопасность и функция будут чем-то вроде пистолета.

replace_with Ящик действительно предлагает такую функциональность с несколькими вариантами восстановления в случае паники. Судя по документации, авторы остро осознают проблему паники, поэтому, если вам действительно нужна эта функциональность, это может быть хорошим местом для ее получения.