Bỏ qua để đến nội dung

Lỗi Rust E0411: cannot find type Self in this scope (Self nằm ngoài impl)

E0411 nghĩa là bạn đã viết Self ở một chỗ mà Rust không biết nó đại diện cho cái gì. Self là cách viết tắt cho “kiểu mà khối impl (hoặc trait) này nói tới”, nên nó chỉ dùng được bên trong một impl, một trait, hoặc một định nghĩa kiểu. Nguyên nhân thường gặp là một hàm tạo như fn new() -> Self được viết ở cấp cao nhất của file, hoặc một phương thức bạn chép ra khỏi khối impl của nó. Lỗi này phổ biến và sửa nhanh. Khác với E0412, mã đã trở thành E0425 từ Rust 1.93, mã lỗi này không thay đổi: rustc 1.97 vẫn in ra E0411. Trang này thuộc loạt bài về lỗi trình biên dịch.

error[E0411]: cannot find type `Self` in this scope
--> src/main.rs:5:21
|
5 | fn new_counter() -> Self { // ❌ `Self` outside an `impl`
| ----------- ^^^^ `Self` is only available in impls, traits, and type definitions
| |
| `Self` not allowed in a function

Thông báo của trình biên dịch luôn bằng tiếng Anh: “cannot find type Self in this scope” nghĩa là “không tìm thấy kiểu Self trong phạm vi này”. Đây là chương trình gây ra lỗi đó (chú thích được giữ bằng tiếng Anh vì nó xuất hiện nguyên văn trong thông báo lỗi):

struct Counter {
count: u32,
}
fn new_counter() -> Self { // ❌ `Self` outside an `impl`
Counter { count: 0 }
}
fn main() {
let c = new_counter();
println!("{}", c.count);
}

rustc chỉ ra hai thứ trên dòng 5:

  1. Dấu ^^^^ dưới Self: “Self is only available in impls, traits, and type definitions” (Self chỉ có trong impl, trait và định nghĩa kiểu). Đó là quy tắc, gói gọn trong một dòng.
  2. Dấu ----- dưới new_counter: “Self not allowed in a function” (không được dùng Self trong một hàm). Đây là lý do trong trường hợp này: new_counter là một hàm bình thường, không thuộc impl nào.

Bạn có thể biết new_counter trả về một Counter, vì bạn vừa viết Counter { count: 0 }. Nhưng Rust không đọc phần thân để đoán Self nghĩa là gì. Self chỉ có nghĩa nhờ một impl Counter { ... } hoặc trait ... { ... } bao quanh nó, và ở đây không có cái nào.

Giống như chữ “ở đây” trên một tờ giấy nhớ. Trên bản đồ, “bạn đang ở đây” thì có nghĩa. Trên một tờ giấy nằm trên bàn bếp, chẳng ai biết “ở đây” là ở đâu.

Self không phải là một kiểu riêng. Nó là một chỗ trống mà mỗi impl hoặc trait tự điền vào: bên trong impl Counter, Self nghĩa là Counter; bên trong impl Timer, nó nghĩa là Timer; bên trong một trait, nó nghĩa là “bất kỳ kiểu nào cài đặt trait này”. Chính điều đó làm nó hữu ích: bạn có thể đổi tên struct, hoặc chép một phương thức sang impl khác, và Self tự đi theo.

Một hàm tự do không thuộc về kiểu nào, nên chẳng có gì để điền vào. Rust có thể thử đoán từ phần thân, nhưng khi đó chữ ký hàm sẽ phụ thuộc vào code bên trong, mà Rust thì cố ý kiểm tra chữ ký một cách độc lập. Xem Struct để biết chỗ ở quen thuộc của fn new() -> Self.

Cách sửa 1: Chuyển hàm vào một khối impl

Phần tiêu đề “Cách sửa 1: Chuyển hàm vào một khối impl”

Phần lớn trường hợp, hãy chọn cách này. Một hàm tạo ra hoặc làm việc với một kiểu nên nằm trong khối impl của kiểu đó, và ở đó Self hoạt động đúng như bạn mong đợi. Gọi nó bằng Counter::new():

struct Counter {
count: u32,
}
impl Counter {
fn new() -> Self {
Self { count: 0 }
}
fn increment(&mut self) {
self.count += 1;
}
}
fn main() {
let mut c = Counter::new();
c.increment();
c.increment();
println!("{}", c.count);
}

Kết quả:

2
▶ Tự chạy thử

Bên trong impl, bạn có thể dùng Self vừa làm kiểu (-> Self) vừa để tạo giá trị (Self { count: 0 }). Xem Struct.

Chọn cách này khi hàm nên tiếp tục là một hàm tự do, ví dụ một hàm hỗ trợ tạo ra nhiều kiểu, hoặc một hàm nằm ở module khác. Chỉ cần viết tên kiểu thật, Counter, ở chỗ bạn đã viết Self:

struct Counter {
count: u32,
}
fn new_counter(start: u32) -> Counter {
Counter { count: start }
}
fn reset(c: &mut Counter) {
c.count = 0;
}
fn main() {
let mut c = new_counter(10);
println!("{}", c.count);
reset(&mut c);
println!("{}", c.count);
}

Kết quả:

10
0
▶ Tự chạy thử

Đây cũng là cách sửa cho fn reset(c: &mut Self) bị chép ra khỏi một impl: mọi Self trong chữ ký và phần thân đều đổi thành Counter.

Cách sửa 3: Dùng kiểu generic nếu ý bạn là “kiểu bất kỳ”

Phần tiêu đề “Cách sửa 3: Dùng kiểu generic nếu ý bạn là “kiểu bất kỳ””

Chọn cách này khi bạn dùng Self với nghĩa “kiểu nào mà bên gọi muốn”, đó là việc của một generic T, không phải Self. Khai báo <T> cùng các trait mà hàm cần. Ở đây, T: Default cho phép hàm tạo ra giá trị khởi đầu của bất kỳ kiểu nào:

fn fresh_pair<T: Default>() -> (T, T) {
(T::default(), T::default())
}
fn main() {
let numbers: (u32, u32) = fresh_pair();
let words: (String, String) = fresh_pair();
println!("{:?}", numbers);
println!("{:?}", words);
}

Kết quả:

(0, 0)
("", "")
▶ Tự chạy thử

Nếu bạn muốn mỗi kiểu tự quyết định cách được tạo ra, hãy đặt hàm vào một trait: trait Reset { fn fresh() -> Self; }. Bên trong một trait, Self nghĩa là “kiểu cài đặt trait này”. Xem Generic và Trait.

Self { ... } trong main hoặc một hàm tự do. let c = Self { count: 0 }; gây ra E0411 với dòng tiêu đề hơi khác: cannot find struct, variant or union type `Self` in this scope. Cách sửa: viết Counter { count: 0 }, hoặc gọi Counter::new().

Một hàm hỗ trợ lồng bên trong một phương thức. Bên trong impl Counter, một phương thức chứa fn helper() -> Self của riêng nó sẽ nhận mã lỗi khác, E0401: can't use `Self` from outer item (không thể dùng Self của mục bên ngoài). Một hàm nằm trong hàm khác không thừa hưởng impl bên ngoài. Cách sửa: viết Counter trong hàm bên trong, hoặc dùng closure, hoặc chuyển helper lên thành một phương thức riêng trong impl.

Nhầm Self với self. Self viết hoa là kiểu; self viết thường là giá trị mà phương thức được gọi trên đó. Dùng self bên ngoài phương thức là một lỗi khác, E0424. Cách sửa cho cả hai: đảm bảo code nằm bên trong đúng khối impl.

  • E0424: self (viết thường, là giá trị) bị dùng bên ngoài phương thức.
  • E0425: không tìm thấy một giá trị, hàm hoặc (từ Rust 1.93) kiểu có tên bình thường.
  • E0412: mã cũ của “cannot find type”, giờ được báo là E0425.
  • E0405: không tìm thấy một trait, thường do thiếu dòng use.
  • E0401: Self của một impl bên ngoài bị dùng trong hàm lồng bên trong, như ở trê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

1. Bạn có thể dùng Self làm kiểu ở đâu?

2. fn make_user() -> Self ở cấp cao nhất gây ra E0411. Cách sửa thường dùng là gì?

3. Bên trong impl Counter, Self { count: 0 } tạo ra cái gì?

Điểm: 0 / 3