Bawa Paths ke Dalem Scope pake Keyword use
Harus nulis paths lengkap-lengkap buat manggil fungsi tuh rasanya kurang nyaman
dan ngulang-ngulang terus. Di Listing 7-7, entah kita milih absolute atau
relative path buat manggil fungsi add_to_waitlist, tiap kali kita mau manggil
add_to_waitlist kita harus nulis front_of_house sama hosting juga.
Untungnya, ada cara buat nyederhanain proses ini: kita bisa bikin shortcut
(jalan pintas) ke sebuah path pake keyword use sekali aja, dan terus pake
nama yang lebih pendek itu di mana-mana di dalem scope-nya.
Di Listing 7-11, kita bawa modul crate::front_of_house::hosting ke dalem scope
dari fungsi eat_at_restaurant biar kita cuma perlu nentuin
hosting::add_to_waitlist buat manggil fungsi add_to_waitlist di
eat_at_restaurant.
mod front_of_house {
pub mod hosting {
pub fn add_to_waitlist() {}
}
}
use crate::front_of_house::hosting;
pub fn eat_at_restaurant() {
hosting::add_to_waitlist();
}
useNambahin use sama sebuah path di dalem sebuah scope itu mirip kayak bikin
symbolic link di sistem file. Dengan nambahin
use crate::front_of_house::hosting di crate root, hosting sekarang jadi
nama yang valid di scope itu, seolah-olah modul hosting itu didefinisikan di
crate root. Path yang dibawa ke scope pake use juga bakal nge-cek privasi,
sama kayak path lainnya.
Perhatiin ya kalau use cuma bikin shortcut buat scope tertentu di mana use
itu dipanggil. Listing 7-12 mindahin fungsi eat_at_restaurant ke dalem anak
modul baru namanya customer, yang mana itu beda scope dari statement use-nya,
jadi body fungsinya nggak bakal bisa di-compile.
mod front_of_house {
pub mod hosting {
pub fn add_to_waitlist() {}
}
}
use crate::front_of_house::hosting;
mod customer {
pub fn eat_at_restaurant() {
hosting::add_to_waitlist();
}
}
use cuma berlaku di scope tempat dia ditaruh.Error compiler nunjukin kalau shortcut-nya udah nggak berlaku lagi di dalem
modul customer:
$ cargo build
Compiling restaurant v0.1.0 (file:///projects/restaurant)
error[E0433]: failed to resolve: use of unresolved module or unlinked crate `hosting`
--> src/lib.rs:11:9
|
11 | hosting::add_to_waitlist();
| ^^^^^^^ use of unresolved module or unlinked crate `hosting`
|
= help: if you wanted to use a crate named `hosting`, use `cargo add hosting` to add it to your `Cargo.toml`
help: consider importing this module through its public re-export
|
10 + use crate::hosting;
|
warning: unused import: `crate::front_of_house::hosting`
--> src/lib.rs:7:5
|
7 | use crate::front_of_house::hosting;
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
= note: `#[warn(unused_imports)]` on by default
For more information about this error, try `rustc --explain E0433`.
warning: `restaurant` (lib) generated 1 warning
error: could not compile `restaurant` (lib) due to 1 previous error; 1 warning emitted
Perhatiin ada warning juga yang bilang kalau use-nya nggak dipake di scope-nya!
Buat benerin masalah ini, pindahin use-nya ke dalem modul customer juga,
atau rujuk shortcut yang ada di modul induk pake super::hosting dari dalem
anak modul customer.
Bikin Paths use yang Idiomatik
Di Listing 7-11, kita mungkin mikir kenapa kita nulis
use crate::front_of_house::hosting terus manggil hosting::add_to_waitlist di
eat_at_restaurant, bukannya nulis path use sampe ke fungsi add_to_waitlist
buat dapet hasil yang sama, kayak di Listing 7-13.
mod front_of_house {
pub mod hosting {
pub fn add_to_waitlist() {}
}
}
use crate::front_of_house::hosting::add_to_waitlist;
pub fn eat_at_restaurant() {
add_to_waitlist();
}
add_to_waitlist ke dalem scope pake use, yang nggak idiomatikWalaupun Listing 7-11 sama Listing 7-13 ngelakuin tugas yang sama, Listing 7-11
adalah cara yang idiomatik buat bawa sebuah fungsi ke dalem scope pake use.
Bawa modul induk dari sebuah fungsi ke dalem scope pake use artinya kita
harus nyebutin modul induknya pas manggil fungsi itu. Nyebutin modul induk pas
manggil fungsi bikin jelas kalau fungsi itu nggak didefinisikan secara lokal,
tapi tetep minimalisir pengulangan nulis absolute path-nya. Kode di Listing 7-13
bikin nggak jelas di mana add_to_waitlist itu sebenernya didefinisikan.
Sebaliknya, pas kita bawa structs, enums, dan item lain pake use, itu
idiomatik buat nyebutin full path-nya. Listing 7-14 nunjukin cara idiomatik
buat bawa struct HashMap dari standard library ke dalem scope dari sebuah
binary crate.
use std::collections::HashMap;
fn main() {
let mut map = HashMap::new();
map.insert(1, 2);
}
HashMap ke dalem scope pake cara yang idiomatikNggak ada alesan kuat sih di balik idiom ini: ini cuma konvensi yang udah muncul, dan orang-orang udah kebiasa baca sama nulis kode Rust dengan cara kayak gini.
Pengecualian buat idiom ini adalah kalau kita bawa dua item yang namanya sama ke
dalem scope pake statement use, karena Rust nggak ngebolehin itu. Listing 7-15
nunjukin gimana cara bawa dua tipe Result yang namanya sama tapi modul induknya
beda ke dalem scope, dan gimana cara merujuk ke mereka.
use std::fmt;
use std::io;
fn function1() -> fmt::Result {
// --snip--
Ok(())
}
fn function2() -> io::Result<()> {
// --snip--
Ok(())
}
Kayak yang bisa kita liat, pake modul induk bisa ngebedain kedua tipe Result
ini. Kalau kita malah nulis use std::fmt::Result sama use std::io::Result,
kita bakal punya dua tipe Result di scope yang sama, dan Rust nggak bakal tau
mana yang kita maksud pas kita pake nama Result.
Ngasih Nama Baru pake Keyword as
Ada solusi lain buat masalah bawa dua tipe dengan nama yang sama ke dalem scope
yang sama pake use: setelah path, kita bisa nambahin as sama nama lokal baru,
atau alias, buat tipe itu. Listing 7-16 nunjukin cara lain buat nulis kode
di Listing 7-15 dengan nge-rename salah satu dari tipe Result itu pake as.
use std::fmt::Result;
use std::io::Result as IoResult;
fn function1() -> Result {
// --snip--
Ok(())
}
fn function2() -> IoResult<()> {
// --snip--
Ok(())
}
asDi statement use yang kedua, kita milih nama baru IoResult buat tipe
std::io::Result, yang nggak bakal bentrok sama Result dari std::fmt yang
juga udah kita bawa ke dalem scope. Listing 7-15 sama Listing 7-16 sama-sama
dianggap idiomatik, jadi milih yang mana itu terserah kita!
Re-exporting Nama pake pub use
Pas kita bawa sebuah nama ke dalem scope pake keyword use, nama itu private
buat scope tempat kita nge-import dia. Buat ngebolehin kode di luar scope itu
buat ngerujuk ke nama itu seolah-olah nama itu didefinisikan di scope tersebut,
kita bisa gabungin pub sama use. Teknik ini namanya re-exporting karena
kita bawa item ke dalem scope tapi juga nge-ekspos item itu biar orang lain
bisa bawa item itu ke scope mereka.
Listing 7-17 nunjukin kode di Listing 7-11 dengan use di modul root diganti
jadi pub use.
mod front_of_house {
pub mod hosting {
pub fn add_to_waitlist() {}
}
}
pub use crate::front_of_house::hosting;
pub fn eat_at_restaurant() {
hosting::add_to_waitlist();
}
pub useSebelum perubahan ini, kode eksternal harus manggil fungsi add_to_waitlist
pake path restaurant::front_of_house::hosting::add_to_waitlist(), yang juga
bakal mewajibkan modul front_of_house buat ditandain sebagai pub. Sekarang,
karena pub use ini udah nge-re-export modul hosting dari modul root,
kode eksternal bisa pake path restaurant::hosting::add_to_waitlist() sebagai
gantinya.
Re-exporting berguna pas struktur internal kode kita itu beda dari gimana
programmer yang manggil kode kita bakal mikirin soal domain-nya. Misalnya, di
analogi restoran ini, orang yang jalanin restorannya mikirnya “front of house”
sama “back of house.” Tapi pelanggan yang dateng ke restoran mungkin nggak bakal
mikirin bagian-bagian restoran pake istilah-istilah itu. Pake pub use, kita
bisa nulis kode kita pake satu struktur tapi nge-ekspos struktur yang beda.
Ngelakuin ini bikin library kita terorganisir dengan baik buat programmer yang
ngerjain library-nya dan juga buat programmer yang manggil library-nya. Kita
bakal liat contoh lain dari pub use dan gimana pengaruhnya ke dokumentasi crate
kita di “Ngekspor API Public yang Nyaman pake pub use” di Bab 14.
Pake Package Eksternal
Di Bab 2, kita bikin project game tebak angka yang pake package eksternal
namanya rand buat dapet angka random. Buat pake rand di project kita, kita
nambahin baris ini ke Cargo.toml:
rand = "0.8.5"
Nambahin rand sebagai dependency (dependensi) di Cargo.toml ngasih tau
Cargo buat download package rand sama dependensinya dari crates.io
terus nyediain rand buat project kita.
Terus, buat bawa definisi rand ke dalem scope package kita, kita nambahin
baris use yang dimulai dari nama crate-nya, rand, dan nge-list item-item
yang mau kita bawa ke dalem scope. Inget kan di “Menghasilkan Angka Random”
di Bab 2, kita bawa trait Rng ke dalem scope terus manggil fungsi
rand::thread_rng:
use std::io;
use rand::Rng;
fn main() {
println!("Guess the number!");
let secret_number = rand::thread_rng().gen_range(1..=100);
println!("The secret number is: {secret_number}");
println!("Please input your guess.");
let mut guess = String::new();
io::stdin()
.read_line(&mut guess)
.expect("Failed to read line");
println!("You guessed: {guess}");
}
Anggota komunitas Rust udah bikin sangat banyak package yang tersedia di
crates.io, dan masukin salah satu dari mereka ke package
kita itu ngelibatin langkah-langkah yang sama: daftarin mereka di file
Cargo.toml package kita terus pake use buat bawa item dari crate mereka ke
dalem scope.
Perhatiin ya kalau standard library std itu juga sebuah crate yang eksternal
buat package kita. Karena standard library udah dipaket bareng bahasa Rust, kita
nggak perlu ngubah Cargo.toml buat masukin std. Tapi kita tetep perlu
ngerujuk ke dia pake use buat bawa item-item dari sana ke dalem scope package
kita. Misalnya, buat HashMap kita bakal pake baris ini:
#![allow(unused)]
fn main() {
use std::collections::HashMap;
}
Ini adalah absolute path yang dimulai dari std, nama dari crate standard
library.
Pake Nested Paths Buat Ngerapihin Daftar use yang Panjang
Kalau kita pake banyak item yang didefinisikan di crate yang sama atau modul
yang sama, nulisin tiap item di barisnya sendiri-sendiri bakal menuhin tempat
secara vertikal di file kita. Misalnya, dua statement use ini yang kita pake
di game tebak angka di Listing 2-4 bawa item-item dari std ke dalem scope:
use rand::Rng;
// --snip--
use std::cmp::Ordering;
use std::io;
// --snip--
fn main() {
println!("Guess the number!");
let secret_number = rand::thread_rng().gen_range(1..=100);
println!("The secret number is: {secret_number}");
println!("Please input your guess.");
let mut guess = String::new();
io::stdin()
.read_line(&mut guess)
.expect("Failed to read line");
println!("You guessed: {guess}");
match guess.cmp(&secret_number) {
Ordering::Less => println!("Too small!"),
Ordering::Greater => println!("Too big!"),
Ordering::Equal => println!("You win!"),
}
}
Sebagai gantinya, kita bisa pake nested paths (path bersarang) buat bawa item-
item yang sama ke dalem scope dalam satu baris. Kita lakuin ini dengan nulisin
bagian yang sama dari path-nya, diikuti sama dua titik dua (::), terus kurung
kurawal di sekitar list dari bagian-bagian path yang beda, kayak yang ditunjukin
di Listing 7-18.
use rand::Rng;
// --snip--
use std::{cmp::Ordering, io};
// --snip--
fn main() {
println!("Guess the number!");
let secret_number = rand::thread_rng().gen_range(1..=100);
println!("The secret number is: {secret_number}");
println!("Please input your guess.");
let mut guess = String::new();
io::stdin()
.read_line(&mut guess)
.expect("Failed to read line");
let guess: u32 = guess.trim().parse().expect("Please type a number!");
println!("You guessed: {guess}");
match guess.cmp(&secret_number) {
Ordering::Less => println!("Too small!"),
Ordering::Greater => println!("Too big!"),
Ordering::Equal => println!("You win!"),
}
}
Di program yang lebih gede, bawa banyak item ke dalem scope dari crate atau modul
yang sama pake nested paths bisa ngurangin sekali jumlah statement use
terpisah yang dibutuhin!
Kita bisa pake nested path di level mana pun di dalem sebuah path, yang berguna
sekali pas ngegabungin dua statement use yang nge-share subpath. Misalnya,
Listing 7-19 nunjukin dua statement use: satu yang bawa std::io ke dalem
scope dan satu lagi yang bawa std::io::Write ke dalem scope.
use std::io;
use std::io::Write;
use di mana salah satunya adalah subpath dari yang lainBagian yang sama dari kedua path ini adalah std::io, dan itu adalah path
lengkap pertama. Buat ngegabungin dua path ini jadi satu statement use, kita
bisa pake self di dalem nested path, kayak yang ditunjukin di Listing 7-20.
use std::io::{self, Write};
useBaris ini bawa std::io sama std::io::Write ke dalem scope.
Operator Glob
Kalau kita mau bawa semua item public yang didefinisikan di sebuah path ke
dalem scope, kita bisa nulis path itu diikuti sama operator glob *:
#![allow(unused)]
fn main() {
use std::collections::*;
}
Statement use ini bawa semua item public yang didefinisikan di
std::collections ke dalem scope saat ini. Hati-hati ya pas pake operator
glob! Glob bisa bikin kita lebih susah buat tau nama apa aja yang ada di dalem
scope dan di mana nama yang dipake di program kita itu didefinisikan. Selain itu,
kalau dependensinya ngerubah definisi mereka, apa yang kita import juga ikut
berubah, yang bisa memicu error compiler pas kita upgrade dependensi kalau
dependensinya nambahin definisi dengan nama yang sama kayak definisi punya kita
di scope yang sama, misalnya.
Operator glob sering sekali dipake pas lagi testing buat bawa semua hal yang
mau di-test ke dalem modul tests; kita bakal bahas itu di “Gimana Cara Nulis
Test” di Bab 11. Operator glob juga kadang dipake sebagai bagian
dari pola prelude: liat dokumentasi standard library
buat info lebih lanjut soal pola itu.