Lỗi Rust E0368: binary assignment operation += cannot be applied to type (không dùng được +=)
E0368 nghĩa là bạn dùng một toán tử kết hợp như +=, -= hay *= trên một kiểu chưa hỗ trợ nó. Đây là người anh em += của E0369. Struct của bạn không tự động có +=, kể cả khi nó đã hỗ trợ +. Cách sửa thường là một khối impl AddAssign ngắn, còn với số nằm sau một tham chiếu thì chỉ cần một dấu *. Các lỗi khác trong loạt bài này nằm ở lỗi trình biên dịch Rust.
Thông báo lỗi
Phần tiêu đề “Thông báo lỗi”error[E0368]: binary assignment operation `+=` cannot be applied to type `Money` --> src/main.rs:8:5 |8 | wallet += tip; // ❌ Money doesn't know how to do += | ------^^^^^^^ | | | cannot use `+=` on type `Money` |note: an implementation of `AddAssign` might be missing for `Money` --> src/main.rs:1:1 |1 | struct Money { | ^^^^^^^^^^^^ must implement `AddAssign`Trình biên dịch báo rằng không thể dùng += trên kiểu Money, vì Money chưa cài đặt AddAssign.
Đây là chương trình gây ra lỗi:
struct Money { cents: i64,}
fn main() { let mut wallet = Money { cents: 500 }; let tip = Money { cents: 150 }; wallet += tip; // ❌ Money doesn't know how to do += println!("{}", wallet.cents);}Chú thích ở dòng 8 giữ tiếng Anh vì nó xuất hiện trong thông báo lỗi; nó nói rằng Money không biết cách làm +=.
Toán tử ở dòng đầu tiên có thể khác, nhưng vẫn là cùng một lỗi. p -= step đòi SubAssign, còn p *= 2 đòi MulAssign<{integer}>, nghĩa là “nhân rồi gán với một số nguyên”.
Ý nghĩa
Phần tiêu đề “Ý nghĩa”Nhãn dưới vế trái cho biết điều gì sai: “cannot use += on type Money” (không dùng được += trên kiểu Money). Sau đó dòng note: chỉ vào struct của bạn và nói nó phải cài đặt AddAssign. Đó là trait đứng sau +=.
Hãy nghĩ tới một con heo đất. Bỏ một đồng xu vào là một hành động rõ ràng. Nhưng nếu ai đó đưa bạn một cái hộp ghi “Tiền” và bảo “cộng cái hộp này vào nó”, bạn cần biết cộng thế nào: cộng số xu lại với nhau? chồng hai cái hộp lên nhau? Rust cần bạn viết quy tắc đó ra một lần.
Vì sao Rust làm vậy
Phần tiêu đề “Vì sao Rust làm vậy”Mỗi toán tử kết hợp thật ra là một lời gọi phương thức được ngụy trang. a += b gọi AddAssign::add_assign(&mut a, b), a -= b gọi SubAssign::sub_assign, v.v. Tất cả các trait này nằm trong std::ops. Các kiểu số đã cài đặt sẵn chúng. Struct mới của bạn thì chưa có cái nào, và không có #[derive] cho toán tử số học.
Đây là phần khiến nhiều người ngạc nhiên: Add và AddAssign là hai trait riêng biệt. Kể cả khi Point đã cài đặt Add, p += q vẫn gây E0368. Lý do là chúng làm những việc khác nhau. a + b nhận hai giá trị và trả về một giá trị mới. a += b thay đổi a tại chỗ qua &mut self và không trả về gì. Thay đổi tại chỗ có thể rẻ hơn nhiều: += của String nối thêm vào bộ đệm sẵn có thay vì tạo một chuỗi mới. Rust để mỗi kiểu tự chọn, nên nó không bao giờ lặng lẽ biến += thành + cho bạn.
Các trait toán tử được giải thích ở Các trait thông dụng và Trait nâng cao.
Cách sửa
Phần tiêu đề “Cách sửa”Cách sửa 1: Cài đặt AddAssign (và các trait anh em)
Phần tiêu đề “Cách sửa 1: Cài đặt AddAssign (và các trait anh em)”Chọn cách này khi += trên kiểu của bạn có ý nghĩa rõ ràng, như cộng tiền hay dời một vị trí. Bạn viết một phương thức thay đổi self. Thêm SubAssign, MulAssign và các trait khác theo cùng cách khi cần:
use std::ops::{AddAssign, SubAssign};
#[derive(Debug)]struct Money { cents: i64,}
impl AddAssign for Money { fn add_assign(&mut self, other: Money) { self.cents += other.cents; }}
impl SubAssign for Money { fn sub_assign(&mut self, other: Money) { self.cents -= other.cents; }}
fn main() { let mut wallet = Money { cents: 500 }; wallet += Money { cents: 150 }; // được tiền boa wallet -= Money { cents: 200 }; // mua bữa trưa println!("{:?}", wallet);}Kết quả:
Money { cents: 450 }Để ý &mut self và không có kiểu trả về. Đó là toàn bộ khác biệt so với Add. Bên trong, self.cents += other.cents chạy được vì i64 đã có AddAssign.
Cách sửa 2: Đã có Add? Viết a = a + b, hoặc dùng lại nó
Phần tiêu đề “Cách sửa 2: Đã có Add? Viết a = a + b, hoặc dùng lại nó”Chọn cách này khi kiểu của bạn đã cài đặt Add (có thể sau khi sửa E0369). Nếu kiểu là Copy, bạn có thể viết đơn giản pos = pos + step, chỉ cần Add. Hoặc cài đặt AddAssign trong một dòng bằng cách gọi Add của bạn, để += cũng chạy được:
use std::ops::{Add, AddAssign};
#[derive(Debug, Clone, Copy)]struct Point { x: i32, y: i32,}
impl Add for Point { type Output = Point;
fn add(self, other: Point) -> Point { Point { x: self.x + other.x, y: self.y + other.y } }}
impl AddAssign for Point { fn add_assign(&mut self, other: Point) { *self = *self + other; // dùng lại Add }}
fn main() { let mut pos = Point { x: 0, y: 0 }; let step = Point { x: 1, y: 2 }; pos = pos + step; // chỉ cần Add pos += step; // cần AddAssign println!("{:?}", pos);}Kết quả:
Point { x: 2, y: 4 }*self = *self + other sao chép giá trị hiện tại ra, cộng, rồi ghi kết quả trở lại. Cách này chỉ chạy được vì Point là Copy. Với một kiểu sở hữu dữ liệu trên heap (như có một trường Vec), hãy viết phiên bản thay đổi tại chỗ như ở Cách sửa 1.
Cách sửa 3: Chuỗi: s += &t hoặc push_str
Phần tiêu đề “Cách sửa 3: Chuỗi: s += &t hoặc push_str”Chọn cách này khi vế trái là một chuỗi. += chạy được trên một String sở hữu, và vế phải phải là &str. Có hai trường hợp hay làm người ta vấp:
cannot be applied to type `&str`: vế trái là một slice chuỗi như"Hello, ". Một&strlà góc nhìn chỉ đọc, nên nó không thể dài thêm. Hãy biến nó thànhStringtrước.s += tkhitcũng làStringthì không phải E0368. Đó là E0308, “expected&str, foundString”, kèm helps += &t. Chỉ cần mượn vế phải.
fn main() { let mut greeting = String::from("Xin chào, "); // sở hữu, nên dài thêm được let name = String::from("Ferris");
greeting += &name; // mượn vế phải greeting.push_str("!"); // cùng việc đó, viết dưới dạng phương thức
let mut tag = "cua".to_string(); // &str ở vế trái? biến nó thành String tag += " #1";
println!("{greeting}"); println!("{tag}"); println!("{name} vẫn dùng được");}Kết quả:
Xin chào, Ferris!cua #1Ferris vẫn dùng đượcVì vế phải chỉ được mượn, name vẫn là của bạn sau đó. += và push_str làm y hệt nhau; dùng cách nào đọc dễ hơn. Xem Chuỗi.
Cách sửa 4: Thêm ràng buộc T: AddAssign trong code generic
Phần tiêu đề “Cách sửa 4: Thêm ràng buộc T: AddAssign trong code generic”Chọn cách này khi thông báo lỗi ghi cannot be applied to type `T`. Bên trong một hàm generic, T có thể là bất kỳ kiểu nào, kể cả kiểu không có +=. Dòng help của rustc nói chính xác cần thêm gì: consider further restricting type parameter `T` with trait `AddAssign` (hãy ràng buộc thêm tham số kiểu T bằng trait AddAssign).
use std::ops::AddAssign;
fn total<T>(items: &[T]) -> Twhere T: Copy + Default + AddAssign,{ let mut sum = T::default(); for &item in items { sum += item; } sum}
fn main() { println!("{}", total(&[1, 2, 3])); println!("{}", total(&[0.5, 0.25]));}Kết quả:
60.75Giờ total chạy được với mọi kiểu có thể sao chép, có giá trị khởi đầu và hỗ trợ +=: số nguyên, số thực, và cả Money của bạn khi nó cài đặt các trait đó. Với số thuần, items.iter().sum() làm cùng việc chỉ trong một dòng. Xem Generic.
Thường gặp ở đâu
Phần tiêu đề “Thường gặp ở đâu”Đếm bằng HashMap. let count = counts.entry(word).or_insert(0); rồi count += 1; cho ra cannot be applied to type `&mut i32`. count là một tham chiếu, không phải con số. Dòng help của rustc: “+= can be used on i32 if you dereference the left-hand side” (+= dùng được trên i32 nếu bạn giải tham chiếu vế trái). Cách sửa: *count += 1;. Xem HashMap.
Nhân một struct với một số. p *= 2 cần MulAssign<{integer}>, vì vế phải là một con số, không phải một Point khác. Cách sửa: cài đặt trait cho đúng kiểu vế phải đó:
impl std::ops::MulAssign<i32> for Point { fn mul_assign(&mut self, factor: i32) { self.x *= factor; self.y *= factor; }}Cộng dồn trong vòng lặp. total += item.price khi price là kiểu Money của riêng bạn. Cách sửa 1 giúp nó chạy được. Nếu bạn thích viết prices.iter().copied().sum(), thì cái đó lại cần trait Sum, và nếu thiếu nó bạn sẽ gặp E0277 (“a value of type Money cannot be made by summing an iterator”).
Lỗi liên quan
Phần tiêu đề “Lỗi liên quan”- E0369: binary operation cannot be applied: cùng vấn đề cho
+,==,<và các toán tử không gán khác. - E0308: mismatched types: lỗi bạn gặp với
s += tkhi cả hai làString(hãy mượn:s += &t). - E0277: trait bound not satisfied:
+=có cho kiểu vế trái, nhưng không với kiểu vế phải này, nhưcannot add-assign `{float}` to `{integer}`vớitotal += 2.5khitotallà số nguyên. - E0384: cannot assign twice:
x += 1trên một biến khai báo không cómut. Toán tử không sao, chỉ là biến không khả biến. - Tất cả lỗi trình biên dịch: danh sách đầy đủ các trang lỗi.
Kiểm tra nhanh
Phần tiêu đề “Kiểm tra nhanh”Kiểm tra nhanh
1. Point đã cài đặt Add, nhưng p += q vẫn gây E0368. Vì sao?
2. count đến từ map.entry(word).or_insert(0). Dòng nào biên dịch được?
3. s và t đều là String. Bạn nối t vào s thế nào?
Điểm: 0 / 3
Đọc thêm
Phần tiêu đề “Đọc thêm”- E0368 — Rust error codes index (tiếng Anh): giải thích chính thức của lỗi này.
std::ops::AddAssign— tài liệu thư viện chuẩn (tiếng Anh): trait đứng sau+=, kèm ví dụ;SubAssign,MulAssignvà các trait còn lại được liệt kê trongstd::ops(tiếng Anh).- Advanced Traits — The Rust Book (tiếng Anh): nạp chồng toán tử với
std::opsvà tham số kiểu mặc định nhưRhs. - Storing UTF-8 Encoded Text with Strings — The Rust Book (tiếng Anh): vì sao chuỗi dài thêm bằng
push_strvà+nhận&strở vế phải. - Operator Overloading — Rust by Example (tiếng Anh): ví dụ ngắn chạy được về cài đặt các trait toán tử cho kiểu của riêng bạn.