Связь между AddAssign и оператором '+ =' - PullRequest
3 голосов
/ 11 июля 2020

До сих пор я понимал, что в Rust операторы в основном являются синтаксисом c сахаром для вызовов методов-признаков. В частности, я думал, что a += b эквивалентно написанию a.add_assign(b). Сегодня я был очень удивлен, услышав от ржавчины c (1.44.1) следующее:

error[E0368]: binary assignment operation `+=` cannot be applied to type `&mut u8`
 --> src/main.rs:2:5
  |
2 |     a += b;
  |     -^^^^^
  |     |
  |     cannot use `+=` on type `&mut u8`
  |
help: `+=` can be used on 'u8', you can dereference `a`
  |
2 |     *a += b;
  |     ^^

Код, ответственный за сообщение об ошибке: ( Playground )

fn test_add_assign(a: &mut u8, b: u8) {
    a += b;
}

fn main() {
    let mut test = 1;
    test_add_assign(&mut test, 1);
    assert_eq!(test, 2);
}

Теперь компилятор исправен, запись *a += b работает, а также правильно присваивает новую переменную. Однако, к моему удивлению, a.add_assign(b) также отлично работает без необходимости разыменования a ( Playground ):

fn test_add_assign(a: &mut u8, b: u8) {
    a.add_assign(b);
}

Учитывая, что документация для AddAssign просто указывает

Оператор присваивания сложения +=.

Мне интересно: какова связь между AddAssign и оператором += , если это не синтаксис c сахар для вызова метода черты?

Ответы [ 2 ]

2 голосов
/ 11 июля 2020

Я думал, что a += b эквивалентно записи a.add_assign(b).

Не совсем так, a += b фактически переводится в ::std::ops::AddAssign::add_assign(&mut a, b). В вашем примере это означает, что вы должны передать &mut &mut u8 в качестве первого параметра.

Если подумать, это имеет смысл. Стандартное присвоение целочисленной переменной i записывается как i = 3;. Если вы хотите вместо этого сделать это вызовом функции, вам нужно передать изменяемую ссылку на i функции, чтобы она действительно могла изменить значение i. То же самое относится и к расширенным присвоениям.

Обратите внимание, что синтаксис вызова метода a.add_assign(b) работает в этом случае, потому что вызовы методов обрабатывают получателя особым образом . Компилятор ищет соответствующий метод путем неявного заимствования и разыменования получателя до тех пор, пока не будет найдено совпадение. Вызов методов для признаков с параметром типа снова является особенным, так как поиск может даже продолжить поиск совпадений для других параметров с этим методом (что, я думаю, не задокументировано в справочнике Rust. в это время).

1 голос
/ 11 июля 2020

Вы более или менее правы.

Проблема, которая, я думаю, сбивает вас с толку, - это automati c deref в вызовах функций методов. Он подробно описан в , этот другой вопрос , но в основном он говорит, что вы можете вызвать функцию-член либо со значением, либо ссылкой, либо ссылкой на ссылку, и это будет просто работать:

let x = 42;
let _ = x.to_string(); //ok
let _ = (&x).to_string(); //ok
let r = &x;
let _ = r.to_string(); //ok
let _ = (*r).to_string(); //ok

Но при использовании операторов автомат c deref не применяется. Итак:

let mut x = 42;
x += 1; //ok;
x.add_assign(1); //ok
let r: &mut i32 = &mut x;
*r += 1; //ok
r += 1; //error: &mut i32 does not implement AddAssign
r.add_assign(1); //ok: r is auto-dereffed

Обратите внимание, что левое выражение += должно быть изменяемым значением (rvalue), а не ссылкой на это значение. Тогда, когда вы пишете a += b, это эквивалентно AddAssign::add_assign(&mut a, b)

...