Я пытался реализовать черту From
для пользовательской структуры, чтобы построить ее из ссылки на другую структуру:
struct Base { ...properties }
struct Derived { ...other_properties }
impl From<&Base> for Derived {
fn from(value: &Base) -> Self {
Self { ... }
}
}
Все было нормально, пока я не попытался вызвать его внутри функции с изменяемой ссылкой на экземпляр Base
в качестве аргумента:
fn foo(base: &mut Base) {
...do stuff on base
let derived = Derived::from(base);
}
Я думал, что компилятор сможет определить, что ссылки имеют одинаковые типы, и передать их как неизменяемые, а не изменяемые, и все будет хорошо, но вместо этого у меня возникла ошибка компиляции:
связанная черта
Derived: From<&mut Base>
не удовлетворена чертаFrom<&Base>
реализована дляDerived
Затем я попытался воспроизвести проблему с помощью своего собственного кода, поэтому создал признак CustomFrom
с одной функцией, имеющей ту же сигнатуру, что и from
из признака From
:
trait CustomFrom<T> {
fn custom_from(base: T) -> Self;
}
И реализовал это:
impl CustomFrom<&Base> for Derived {
fn custom_from(value: &Base) -> Self {
Self { ... }
}
}
и назвал его так же, как и оригинал from
:
fn foo(base: &mut Base) {
...do stuff on base
let derived = Derived::from(base);
let custom = Derived::custom_from(base);
}
За исключением того, что на этот раз компилятор согласился с использованием моего пользовательского признака.
Я знаю, что могу решить проблему, позвонив с помощью Derived::from(& *base);
, но хотелось бы знать:
В чем разница между этими двумя чертами?
Почему компилятор смог использовать ссылку как неизменяемую с моей чертой, но не со стандартной?
Полный минимальный пример:
struct Base {
a: u8,
b: u8,
c: u8,
d: u8,
e: u8,
f: u8,
}
trait CustomFrom<T> {
fn custom_from(param: T) -> Self;
}
impl Base {
fn new(a: u8, b: u8, c: u8, d: u8, e: u8, f: u8) -> Self {
Self { a, b, c, d, e, f }
}
fn a(&self) -> &u8 {
&self.a
}
fn c(&self) -> &u8 {
&self.c
}
fn d(&self) -> &u8 {
&self.d
}
}
struct Derived {
a: u8,
c: u8,
d: u8,
}
impl From<&Base> for Derived {
fn from(base: &Base) -> Self {
Self {
a: *base.a(),
c: *base.c(),
d: *base.d()
}
}
}
impl CustomFrom<&Base> for Derived {
fn custom_from(base: &Base) -> Self {
Self {
a: *base.a(),
c: *base.c(),
d: *base.d()
}
}
}
fn main() {
let mut base = Base::new(1, 2, 3, 4, 5, 6);
let ex = Derived::from(&base);
}
fn foo(base: &mut Base) {
let test1 = Derived::from(base);
let test2 = Derived::custom_from(base);
}
fn bar(mut base: Base) {
let test1 = Derived::from(&base);
let test2 = Derived::custom_from(&base);
}
Ваша репликация CustomFrom
, похоже, работает, потому что компилятор может сделать дополнительные выводы, если признак имеет только одну реализацию. Если вы введете другую реализацию, например impl CustomFrom<()> for Derived
, то она также не будет работать с &mut Base
:
error[E0277]: the trait bound `Derived: CustomFrom<&mut Base>` is not satisfied
--> src/main.rs:72:17
|
72 | let test2 = Derived::custom_from(base);
| ^^^^^^^ the trait `CustomFrom<&mut Base>` is not implemented for `Derived`
|
= help: the following other types implement trait `CustomFrom<T>`:
<Derived as CustomFrom<&Base>>
<Derived as CustomFrom<()>>
Помимо этого сценария, компилятор обычно не выполняет никаких принуждений (например, &mut T
к &T
), чтобы найти подходящую реализацию признака.
@IvanC это работает только потому, что у вас нет общего реализации, и, опять же, компилятор может сделать дальнейшие выводы, правильный Into тоже не работает с той же проблемой, что и From
Есть ли у вас источник о том, что именно означает «компилятор может делать дополнительные выводы, если признак имеет только одну реализацию»? Понятно, что вы имеете в виду в данном контексте, но мне было бы интересно более формальное утверждение.
@jthulhu Мне тоже нужен подробный ресурс о том, как это работает. Лучшее, что я могу сделать, это rusc-руководство по определению черт . Формулировка указывает, что если найден один «кандидат», он немедленно переходит к «подтверждению», подходит он или нет (что, я думаю, может включать в себя принуждение), тогда как, если есть несколько кандидатов, он этого не делает (и если ограничения могут если не сводить его к одному кандидату, тогда он либо неудовлетворительный, либо неоднозначный).
Возможно, было бы полезно уточнить, что компилятор не выполняет никаких приведений в позиции аргумента. IE это работает: play.rust-lang.org/…