Как ржавчина переводит изменяемую ссылку в неизменяемую ссылку в общих чертах?

Я пытался реализовать черту 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);
}
Почему Python в конце концов умрет
Почему Python в конце концов умрет
Последние 20 лет были действительно хорошими для Python. Он прошел путь от "просто языка сценариев" до основного языка, используемого для написания...
0
0
51
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

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

Ваша репликация 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), чтобы найти подходящую реализацию признака.

Возможно, было бы полезно уточнить, что компилятор не выполняет никаких приведений в позиции аргумента. IE это работает: play.rust-lang.org/…

Ivan C 20.08.2024 20:22

@IvanC это работает только потому, что у вас нет общего реализации, и, опять же, компилятор может сделать дальнейшие выводы, правильный Into тоже не работает с той же проблемой, что и From

cafce25 20.08.2024 21:52

Есть ли у вас источник о том, что именно означает «компилятор может делать дополнительные выводы, если признак имеет только одну реализацию»? Понятно, что вы имеете в виду в данном контексте, но мне было бы интересно более формальное утверждение.

jthulhu 20.08.2024 22:21

@jthulhu Мне тоже нужен подробный ресурс о том, как это работает. Лучшее, что я могу сделать, это rusc-руководство по определению черт . Формулировка указывает, что если найден один «кандидат», он немедленно переходит к «подтверждению», подходит он или нет (что, я думаю, может включать в себя принуждение), тогда как, если есть несколько кандидатов, он этого не делает (и если ограничения могут если не сводить его к одному кандидату, тогда он либо неудовлетворительный, либо неоднозначный).

kmdreko 20.08.2024 23:06

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