Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

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.

Filename: src/lib.rs
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();
}
Listing 7-11: Bawa sebuah modul ke dalem scope pake use

Nambahin 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.

Filename: src/lib.rs
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();
    }
}
Listing 7-12: Statement 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.

Filename: src/lib.rs
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();
}
Listing 7-13: Bawa fungsi add_to_waitlist ke dalem scope pake use, yang nggak idiomatik

Walaupun 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.

Filename: src/main.rs
use std::collections::HashMap;

fn main() {
    let mut map = HashMap::new();
    map.insert(1, 2);
}
Listing 7-14: Bawa HashMap ke dalem scope pake cara yang idiomatik

Nggak 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.

Filename: src/lib.rs
use std::fmt;
use std::io;

fn function1() -> fmt::Result {
    // --snip--
    Ok(())
}

fn function2() -> io::Result<()> {
    // --snip--
    Ok(())
}
Listing 7-15: Bawa dua tipe dengan nama yang sama ke dalem scope yang sama nuntut kita buat pake modul induk mereka.

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.

Filename: src/lib.rs
use std::fmt::Result;
use std::io::Result as IoResult;

fn function1() -> Result {
    // --snip--
    Ok(())
}

fn function2() -> IoResult<()> {
    // --snip--
    Ok(())
}
Listing 7-16: Nge-rename sebuah tipe pas dibawa ke dalem scope pake keyword as

Di 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.

Filename: src/lib.rs
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();
}
Listing 7-17: Bikin sebuah nama bisa dipake dari scope baru oleh kode lain pake pub use

Sebelum 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:

Filename: 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:

Filename: src/main.rs
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.

Filename: src/main.rs
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!"),
    }
}
Listing 7-18: Nentuin nested path buat bawa beberapa item dengan awalan yang sama ke dalem scope

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.

Filename: src/lib.rs
use std::io;
use std::io::Write;
Listing 7-19: Dua statement use di mana salah satunya adalah subpath dari yang lain

Bagian 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.

Filename: src/lib.rs
use std::io::{self, Write};
Listing 7-20: Ngegabungin path di Listing 7-19 jadi satu statement use

Baris 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.