Lewati ke konten

Error Rust E0401: can't use generic parameters from outer item (T dari luar)

E0401 artinya: sebuah fungsi yang ditulis di dalam fungsi lain mencoba memakai tipe generic milik fungsi luarnya, seperti T, atau Self dari impl tempat ia berada. Rasanya seharusnya bisa, karena fungsi dalamnya ada di situ, di dalam fungsi luar. Tapi di Rust, fn bersarang adalah item terpisah yang tidak bisa melihat T milik fungsi luar. Ini kejutan yang sering muncul saat kamu merapikan fungsi generic dengan memindahkan sebuah fungsi pembantu ke dalamnya, dan perbaikannya cuma butuh semenit. Halaman ini bagian dari panduan error compiler.

error[E0401]: can't use generic parameters from outer item
--> src/main.rs:2:20
|
1 | fn print_all<T: std::fmt::Display>(items: &[T]) {
| - type parameter from outer item
2 | fn show(item: &T) { // ❌ `fn` di dalam tidak bisa melihat `T` dari luar
| ---- ^ use of generic parameter from outer item
| |
| generic parameter used in this inner function
|
= note: nested items are independent from their parent item for everything except for privacy and name resolution
help: try introducing a local generic parameter here
|
2 | fn show<T>(item: &T) { // ❌ `fn` di dalam tidak bisa melihat `T` dari luar
| +++

Ini program yang menghasilkan error tersebut:

fn print_all<T: std::fmt::Display>(items: &[T]) {
fn show(item: &T) { // ❌ `fn` di dalam tidak bisa melihat `T` dari luar
println!("- {}", item);
}
for item in items {
show(item);
}
}
fn main() {
print_all(&["apel", "pir"]);
}

rustc menunjuk ke tiga tempat:

  1. Tanda - di bawah T di baris 1: “type parameter from outer item” (parameter tipe dari item luar). Ini T milik print_all.
  2. Tanda ^ di bawah T di baris 2: “use of generic parameter from outer item” (pemakaian parameter generic dari item luar). Fungsi dalam show mencoba memakai T itu.
  3. Tanda ---- di bawah show: “generic parameter used in this inner function” (parameter generic dipakai di fungsi dalam ini). Inilah fungsi yang tidak boleh melakukannya.

note:-nya menyebut aturannya dalam satu baris: item bersarang berdiri sendiri, terpisah dari induknya (kecuali soal privasi dan pencarian nama). Menaruh fn show di dalam print_all hanya menyembunyikan namanya dari bagian lain file. Itu tidak memberi show akses ke apa pun milik print_all: bukan T-nya, bukan parameternya, bukan variabel lokalnya.

Ini seperti kamar tamu di rumah yang dipakai bersama. Kamarnya ada di dalam rumah, tapi tamunya tidak otomatis dapat kunci kamar tidurmu hanya karena ia tidur di ujung lorong.

Fungsi generic seperti print_all<T> itu semacam cetakan. Rust membuat salinan terpisah untuk setiap T yang kamu pakai saat memanggilnya: satu untuk &str, satu untuk i32, dan seterusnya. Sedangkan fn bersarang dikompilasi sekali, sebagai item sendiri, persis seolah-olah kamu menulisnya di bagian atas file. Jadi di dalam show, tidak ada satu T pun yang bisa dimaksud.

Rust sebenarnya bisa saja membuat fungsi bersarang ikut tersalin bersama induknya, tapi itu akan membuatnya berperilaku sangat berbeda dari fn lainnya. Jadi aturannya dibuat sederhana: fn di dalam hanya tahu apa yang kamu berikan kepadanya, lewat parameter generic dan argumennya sendiri. Kalau kamu ingin sesuatu yang bisa melihat kode di sekitarnya, Rust punya closure untuk itu. Lihat Generics dan Closure.

Perbaikan 1: Beri fungsi dalam parameter generic sendiri

Section titled “Perbaikan 1: Beri fungsi dalam parameter generic sendiri”

Pilih ini di sebagian besar kasus. Deklarasikan <T> juga di fungsi dalam, dengan bound yang sama yang dibutuhkannya. Help dari rustc menyarankan fn show<T>, tapi kalau kamu hanya menambah <T>, kamu mendapat error baru, E0277 (`T` doesn't implement `std::fmt::Display`), karena T yang baru belum punya bound. Salin bound-nya juga:

use std::fmt::Display;
fn print_all<T: Display>(items: &[T]) {
fn show<T: Display>(item: &T) {
println!("- {}", item);
}
for item in items {
show(item);
}
}
fn main() {
print_all(&["apel", "pir"]);
print_all(&[3, 7]);
}

Output:

- apel
- pir
- 3
- 7
▶ Coba sendiri

T di dalam adalah parameter baru yang kebetulan bernama sama. Saat print_all memanggil show(item), Rust mengisinya dengan T dari luar. Kamu bisa menamainya U supaya lebih jelas.

Pilih ini kalau fungsi pembantunya kecil, atau kalau ia juga butuh variabel milik fungsi luar. Closure bukan item terpisah, jadi ia bisa melihat semua yang ada di sekitarnya: T dari luar, parameter fungsinya, dan variabel lokalnya. Di sini closure-nya juga memakai label, sesuatu yang tidak akan pernah bisa dijangkau fn bersarang:

use std::fmt::Display;
fn print_all<T: Display>(label: &str, items: &[T]) {
let show = |item: &T| println!("{}: {}", label, item);
for item in items {
show(item);
}
}
fn main() {
print_all("buah", &["apel", "pir"]);
}

Output:

buah: apel
buah: pir
▶ Coba sendiri

Closure tidak bisa generic dengan sendirinya, tapi di sini memang tidak perlu: di dalam print_all<T>, &T sudah berarti satu tipe konkret untuk setiap salinan fungsinya. Lihat Closure.

Perbaikan 3: Pindahkan fungsinya keluar (atau ke dalam impl)

Section titled “Perbaikan 3: Pindahkan fungsinya keluar (atau ke dalam impl)”

Pilih ini kalau fungsi pembantunya berguna dengan sendirinya, atau kalau error-nya soal Self. Di dalam sebuah method, fn bersarang juga tidak bisa memakai Self, karena dengan cara yang sama ia berada di luar impl:

error[E0401]: can't use `Self` from outer item
--> src/main.rs:7:24
|
5 | impl Inventory {
| ---- `Self` type implicitly declared here, by this `impl`
6 | fn report(&self) {
7 | fn count(inv: &Self) -> usize { // ❌ `fn` di dalam tidak bisa melihat `Self`
| ----- ^^^^ use of `Self` from outer item
| |
| `Self` used in this inner function
|
= note: nested items are independent from their parent item for everything except for privacy and name resolution
help: refer to the type directly here instead
|
7 - fn count(inv: &Self) -> usize { // ❌ `fn` di dalam tidak bisa melihat `Self`
7 + fn count(inv: &Inventory) -> usize { // ❌ `fn` di dalam tidak bisa melihat `Self`
|

Artinya: Self dideklarasikan oleh impl Inventory, tapi fn count di dalam report tidak bisa memakainya, dan help-nya menyarankan menulis nama tipenya langsung. Program yang rusak:

struct Inventory {
items: Vec<String>,
}
impl Inventory {
fn report(&self) {
fn count(inv: &Self) -> usize { // ❌ `fn` di dalam tidak bisa melihat `Self`
inv.items.len()
}
println!("{} barang", count(self));
}
}
fn main() {
let inv = Inventory { items: vec!["tali".to_string()] };
inv.report();
}

Help dari rustc (tulis &Inventory) memang berhasil. Tapi biasanya perbaikan yang lebih rapi adalah memindahkan count naik satu tingkat, sehingga ia menjadi method tersendiri. Di sana, Self dan self bekerja seperti biasa:

struct Inventory {
items: Vec<String>,
}
impl Inventory {
fn count(&self) -> usize {
self.items.len()
}
fn report(&self) {
println!("{} barang", self.count());
}
}
fn main() {
let inv = Inventory {
items: vec!["tali".to_string(), "lampu".to_string()],
};
inv.report();
}

Output:

2 barang
▶ Coba sendiri

Untuk fungsi pembantu generic seperti show, “memindahkannya keluar” berarti fn show<T: Display>(item: &T) di level atas, yang juga bisa dipakai ulang oleh fungsi lain. Lihat Struct dan Fungsi di Rust.

Struct atau enum di dalam fungsi generic. fn group<T>(items: Vec<T>) dengan struct Pair { a: T, b: T } di dalamnya memberi E0401 di setiap T. Perbaikannya: jadikan tipe di dalamnya generic juga, struct Pair<T> { a: T, b: T }, atau pindahkan ke level atas.

const yang bergantung pada T. const SIZE: usize = std::mem::size_of::<T>(); di dalam fungsi generic juga E0401, karena const juga sebuah item. Perbaikannya: pakai let: let size = std::mem::size_of::<T>();.

Fungsi dalam yang memakai variabel lokal. Idenya sama, hanya saja berupa nilai, bukan tipe, dan kodenya berbeda: E0434 (can't capture dynamic environment in a fn item, tidak bisa menangkap lingkungan dinamis di item fn). Perbaikannya: kirim nilainya sebagai argumen, atau pakai closure (Perbaikan 2).

  • E0411: Self dipakai di luar impl atau trait mana pun, misalnya di fn new() -> Self di level atas.
  • E0277: trait bound tidak terpenuhi, yang kamu dapat setelah menulis fn show<T> tanpa menyalin T: Display.
  • E0425: tidak bisa menemukan nilai atau tipe, misalnya tipe yang tidak pernah kamu deklarasikan.
  • E0107: jumlah argumen generic salah, kesalahan lain seputar generics.
  • E0434: fn di dalam memakai variabel lokal milik fungsi luar, ditunjukkan di atas.
  • Semua error compiler: daftar lengkap halaman error.

Kuis singkat

1. Di dalam fn print_all<T>(...), kenapa fn show(item: &T) yang bersarang tidak bisa memakai T?

2. Fungsi pembantu mana di dalam fn print_all<T: Display> yang bisa memakai T dari luar sekaligus variabel lokal fungsi luar?

3. Kamu mengikuti help dari rustc dan menulis fn show<T>(item: &T), lalu mencetak item dengan {}. Apa yang terjadi?

Skor: 0 / 3