Lewati ke konten

Error Rust E0428: the name is defined multiple times (nama didefinisikan dua kali)

E0428 artinya: kamu mendefinisikan dua hal dengan nama yang sama di tempat yang sama: dua fungsi bernama area, dua struct bernama User, atau sebuah enum dan sebuah struct yang sama-sama bernama Status. Ini sering terjadi saat kamu mencoba melakukan overloading pada sebuah fungsi, seperti yang biasa kamu lakukan di Java, C#, atau C++. Rust tidak punya overloading, tapi punya pengganti yang bagus, dan halaman ini menunjukkannya. (Halaman ini bagian dari Penjelasan error compiler Rust.)

error[E0428]: the name `area` is defined multiple times
--> src/main.rs:5:1
|
1 | fn area(width: f64, height: f64) -> f64 {
| --------------------------------------- previous definition of the value `area` here
...
5 | fn area(radius: f64) -> f64 { // ❌ `area` yang kedua
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^ `area` redefined here
|
= note: `area` must be defined only once in the value namespace of this module

Artinya: nama area didefinisikan lebih dari sekali. rustc menunjukkan definisi pertama, lalu definisi kedua yang ditolak.

Ini program yang menghasilkan error tersebut:

fn area(width: f64, height: f64) -> f64 {
width * height
}
fn area(radius: f64) -> f64 { // ❌ `area` yang kedua
3.14159 * radius * radius
}
fn main() {
println!("{}", area(2.0, 3.0));
println!("{}", area(1.0));
}

rustc juga menampilkan error kedua untuk program ini: E0061, “this function takes 2 arguments but 1 argument was supplied” (fungsi ini menerima 2 argumen tapi hanya diberi 1), di area(1.0). Itu hanya efek samping. Rust menyimpan area yang pertama dan mengabaikan yang kedua, jadi pemanggilan dengan satu argumen tidak cocok lagi. Perbaiki E0428, dan E0061 ikut hilang.

rustc menunjuk ke dua tempat:

  1. Baris --- (baris 1): “previous definition of the value area here” (definisi sebelumnya dari nilai area ada di sini). Ini area yang pertama, yang disimpan oleh Rust.
  2. Baris ^^^ (baris 5): “area redefined here” (area didefinisikan ulang di sini). Ini yang kedua, dan yang inilah yang ditolak.
  3. note:-nya bilang area hanya boleh didefinisikan sekali “in the value namespace of this module” (di namespace nilai modul ini). Setiap modul ibarat satu laci, dan di dalam satu laci setiap nama harus unik.

Untuk dua struct, catatannya menyebut “type namespace” (namespace tipe). Rust menyimpan tipe (struct, enum, trait, modul) dan nilai (fungsi, konstanta, static) di dua daftar yang terpisah. Itulah sebabnya mod shapes dan fn shapes bisa hidup berdampingan, tapi dua fn area atau dua struct User tidak bisa.

Bayangkan dua file bernama report.txt di satu folder. Komputermu menolak file kedua, bukan karena ada yang salah dengan isinya, tapi karena nama report.txt tidak lagi bisa memberi tahu file mana yang kamu maksud.

Di Rust, sebuah nama di dalam modul menunjuk ke tepat satu item. Saat kamu menulis area(...), Rust mencari fungsinya hanya berdasarkan namanya, bukan berdasarkan jumlah atau tipe argumennya. Ini membuat kode tetap mudah dibaca: hanya ada satu area yang perlu dicari, dan menambahkan fungsi baru tidak akan pernah diam-diam mengubah fungsi mana yang dipakai oleh pemanggilan lama.

Bahasa yang punya overloading memilih versinya dari argumen, dan aturannya jadi rumit begitu angka bisa dikonversi satu sama lain atau ada nilai default yang ikut campur. Rust memilih untuk tidak melakukannya. Kalau kamu ingin “nama yang sama untuk input yang berbeda”, kamu memakai trait atau generics, tempat pilihannya terlihat dari tipenya. Penjelasan lebih lanjut tentang fungsi ada di Fungsi.

Perbaikan 1: Beri masing-masing nama sendiri

Section titled “Perbaikan 1: Beri masing-masing nama sendiri”

Pilih ini hampir setiap saat. Dua fungsi yang menerima input berbeda biasanya mengerjakan tugas yang berbeda, dan namanya bisa menjelaskan yang mana. Kalau salah satunya adalah salinan lama yang lupa kamu hapus, hapus saja:

fn rectangle_area(width: f64, height: f64) -> f64 {
width * height
}
fn circle_area(radius: f64) -> f64 {
3.14159 * radius * radius
}
fn main() {
println!("{}", rectangle_area(2.0, 3.0));
println!("{}", circle_area(1.0));
}

Output:

6
3.14159
▶ Coba sendiri

Standard library juga melakukan hal yang sama: String::new(), String::with_capacity(10), dan String::from("hi") adalah tiga nama berbeda untuk tiga cara membuat String.

Pilih ini kalau nama pendeknya memang yang paling cocok di setiap konteks, seperti area untuk setiap bentuk, atau User di kode database-mu dan User lain di kode API-mu. Setiap modul punya daftar namanya sendiri, jadi bentroknya hilang. Pemanggil menulis nama modulnya di depan:

mod rectangle {
pub fn area(width: f64, height: f64) -> f64 {
width * height
}
}
mod circle {
pub fn area(radius: f64) -> f64 {
3.14159 * radius * radius
}
}
fn main() {
println!("{}", rectangle::area(2.0, 3.0));
println!("{}", circle::area(1.0));
}

Output:

6
3.14159
▶ Coba sendiri

Jangan lupa pub, atau pemanggilan dari main memberi E0603, “function is private” (fungsinya private). Lihat Modul.

Perbaikan 3: Pakai trait sebagai pengganti overloading

Section titled “Perbaikan 3: Pakai trait sebagai pengganti overloading”

Pilih ini kalau kamu ingin satu pemanggilan yang bekerja untuk banyak tipe, yang biasanya memang tujuan overloading. Buat satu tipe untuk setiap jenis input, dan beri mereka trait yang sama. Sekarang setiap bentuk punya area sendiri, dan Rust memilih yang tepat dari tipe nilainya:

trait Shape {
fn area(&self) -> f64;
}
struct Rectangle {
width: f64,
height: f64,
}
struct Circle {
radius: f64,
}
impl Shape for Rectangle {
fn area(&self) -> f64 {
self.width * self.height
}
}
impl Shape for Circle {
fn area(&self) -> f64 {
3.14159 * self.radius * self.radius
}
}
fn print_area<T: Shape>(shape: &T) {
println!("Luas: {}", shape.area());
}
fn main() {
print_area(&Rectangle { width: 2.0, height: 3.0 });
print_area(&Circle { radius: 1.0 });
}

Output:

Luas: 6
Luas: 3.14159
▶ Coba sendiri

Kodenya lebih banyak daripada Perbaikan 1, jadi ini sepadan kalau kamu punya beberapa bentuk atau beberapa fungsi (area, perimeter, draw) yang semuanya mengikuti pola yang sama. Fungsi generic print_area<T: Shape> bisa dipakai untuk bentuk apa pun yang kamu tambahkan nanti. Lihat Trait dan Generics.

Untuk “fungsi yang sama, dengan atau tanpa argumen tambahan”, ada pilihan yang lebih ringan: terima sebuah Option, seperti fn greet(name: &str, title: Option<&str>), lalu panggil dengan None atau Some("Dr.").

Perbaikan 4: Ganti nama import dengan use ... as ...

Section titled “Perbaikan 4: Ganti nama import dengan use ... as ...”

Pilih ini kalau yang bentrok adalah item buatanmu sendiri dan sebuah baris use. Sebenarnya itu error saudaranya, E0255, dengan judul yang sama:

error[E0255]: the name `Error` is defined multiple times
--> src/main.rs:3:1
|
1 | use std::io::Error;
| -------------- previous import of the type `Error` here
2 |
3 | enum Error {
| ^^^^^^^^^^ `Error` redefined here
|
= note: `Error` must be defined only once in the type namespace of this module
help: you can use `as` to change the binding name of the import
|
1 | use std::io::Error as OtherError;
| +++++++++++++

Artinya: nama Error sudah di-import di baris 1, lalu didefinisikan ulang sebagai enum di baris 3. rustc menyarankan memakai as untuk mengganti nama import-nya.

Beri import itu nama panggilan lokal dengan as. Pilih nama yang menunjukkan asalnya, seperti IoError, daripada nama sementara dari rustc, OtherError:

use std::io::Error as IoError;
use std::io::ErrorKind;
#[derive(Debug)]
enum Error {
NotFound(String),
Invalid(u32),
}
fn main() {
let mine = Error::NotFound(String::from("config.toml"));
let other = Error::Invalid(42);
let disk = IoError::new(ErrorKind::Other, "disk penuh");
println!("{:?}", mine);
println!("{:?}", other);
println!("{}", disk);
}

Output:

NotFound("config.toml")
Invalid(42)
disk penuh
▶ Coba sendiri

Dua baris use yang bentrok, seperti use std::fmt::Result; dan use std::io::Result;, memberi saudara ketiga, E0252 (`Result` reimported here, Result di-import ulang di sini). Perbaikannya sama: use std::io::Result as IoResult;, atau import modulnya (use std::io;) lalu tulis io::Result. Mengganti nama enum buatanmu sendiri (AppError) juga bisa.

Overloading method di blok impl. Dua fn new di impl Timer memberi kode lain, E0592: duplicate definitions with name `new` (definisi ganda dengan nama new). Penyebabnya sama, perbaikannya juga sama: beri nama sesuai tugasnya, seperti Timer::new() dan Timer::with_seconds(30). Lihat Struct.

Copy-paste saat refactoring. Kamu menyalin sebuah fungsi untuk diubah, atau menempelkan struct User dari file lain, lalu lupa menghapus yang lama. Ini juga sering terjadi di test: dua #[test] fn it_works() di satu mod tests memberi E0428. Perbaikannya: hapus salinan lamanya, atau ganti nama test-nya.

Varian enum yang ganda. enum Color { Red, Red } juga memberi E0428, menunjuk ke Red yang kedua, dengan catatan “type namespace of this enum”. Perbaikannya: hapus atau ganti nama varian yang ganda. Lihat Enum.

  • E0061: jumlah argumen salah. Sering muncul tepat setelah E0428 saat kamu mencoba overloading sebuah fungsi.
  • E0425: masalah kebalikannya: nama yang didefinisikan nol kali di tempat kamu memakainya.
  • E0433: path seperti circle::area yang bagian pertamanya (modulnya) tidak ditemukan.
  • E0034: dua trait memberi sebuah tipe method dengan nama yang sama; kamu harus menyebutkan yang mana yang kamu maksud.
  • E0255 (sebuah use bentrok dengan item lokal), E0252 (dua baris use bentrok), E0592 (method ganda di sebuah impl): saudara-saudara yang ditunjukkan di atas.
  • Semua error compiler: daftar lengkap halaman error.

Kuis singkat

1. Kamu menulis fn total(a: u32) dan fn total(a: u32, b: u32) di satu file. Apa yang terjadi?

2. Bisakah mod report dan fn report() berada di modul yang sama?

3. File-mu punya use std::fmt::Result; dan kamu mendefinisikan enum Result. Apa perbaikan yang rapi?

Skor: 0 / 3