Как обернуть заемную стоимость в новый тип, который также является заемной стоимостью?
Я пытаюсь использовать шаблон нового типа, чтобы обернуть уже существующий тип. Этот внутренний тип имеет modify
метод, который позволяет нам работать с заимствованным изменяемым значением в обратном вызове:
struct Val;
struct Inner(Val);
impl Inner {
fn modify<F>(&self, f: F)
where F: FnOnce(&mut Val) -> &mut Val { … }
}
Теперь я хочу предоставить очень похожий метод для моего нового типа Outer
который, однако, не должен работать на Val
с, но снова обертка нового типа WrappedVal
:
struct Outer(Inner);
struct WrappedVal(Val);
impl Outer {
fn modify<F>(&self, f: F)
where
F: FnOnce(&mut WrappedVal) -> &mut WrappedVal,
{
self.0.modify(|v| f(/* ??? */));
}
}
Этот код является сокращенным примером из исходного API. Я не знаю, почему ссылка возвращается из замыкания, возможно, для облегчения создания цепочки, но в этом нет необходимости. Занимает &self
потому что он использует внутреннюю изменчивость - это тип, представляющий периферийный регистр во встроенной системе
Как мне получить &mut WrappedVal
из &mut Val
?
Я пробовал разные вещи, но все были разорены заемщиком. Я не могу пошевелить Val
из изменчивой ссылки, чтобы построить собственное WrappedVal
и я не мог получить время жизни для компиляции, когда экспериментировал с struct WrappedVal(&'? mut Val)
(что я на самом деле не хочу, так как они усложняют реализацию черты).
В конце концов я получил его для компиляции (см. Демонстрацию игровой площадки Rust), используя абсолютный ужас
self.0.modify(|v| unsafe {
(f((v as *mut Val as *mut WrappedVal).as_mut().unwrap()) as *mut WrappedVal as *mut Val)
.as_mut()
.unwrap()
});
но наверняка должен быть лучший путь?
1 ответ
Не существует безопасного способа с вашим текущим определением, и ваш небезопасный код не гарантированно безопасен. Там нет контракта, что макет WrappedVal
совпадает с Val
, хотя это все, что он держит.
Решение не используется unsafe
Не делай этого. Вместо этого оберните ссылку:
struct WrappedVal<'a>(&'a mut Val);
impl Outer {
fn modify<F>(&self, f: F)
where
F: FnOnce(WrappedVal) -> WrappedVal,
{
self.0.modify(|v| f(WrappedVal(v)).0)
}
}
Решение с использованием unsafe
Вы можете утверждать, что ваш тип имеет то же представление, что и тип, который он переносит, что делает указатели совместимыми через repr(transparent)
:
#[repr(transparent)]
struct WrappedVal(given::Val);
impl Outer {
fn modify<F>(&self, f: F)
where
F: FnOnce(&mut WrappedVal) -> &mut WrappedVal,
{
self.0.modify(|v| {
// Insert documentation why **you** think this is safe
// instead of copy-pasting from Stack Overflow
let wv = unsafe { &mut *(v as *mut given::Val as *mut WrappedVal) };
let wv = f(wv);
unsafe { &mut *(wv as *mut WrappedVal as *mut given::Val) }
})
}
}
С repr(transparent)
на месте, два указателя являются взаимозаменяемыми. Я провел быстрый тест с Мири и вашим полным примером и не получил никаких ошибок, но это не серебряная пуля, потому что я ничего не испортил.
Используя библиотеку ref_cast, вы можете написать:
#[derive(RefCast)]
#[repr(transparent)]
struct WrappedVal(Val);
Затем вы можете конвертировать, используя WrappedVal::ref_cast_mut(v)
.