Pisahkan modul di beberapa file

102

Saya ingin memiliki modul dengan beberapa struct di dalamnya, masing-masing dalam filenya sendiri. Menggunakan Mathmodul sebagai contoh:

Math/
  Vector.rs
  Matrix.rs
  Complex.rs

Saya ingin setiap struct berada dalam modul yang sama, yang akan saya gunakan dari file utama saya, seperti:

use Math::Vector;

fn main() {
  // ...
}

Namun sistem modul Rust (yang awalnya agak membingungkan) tidak menyediakan cara yang jelas untuk melakukan ini. Tampaknya hanya memungkinkan Anda untuk memiliki seluruh modul Anda dalam satu file. Apakah ini tidak kasar? Jika tidak, bagaimana cara melakukannya?

starscape
sumber
1
Saya menafsirkan "Saya ingin memiliki modul dengan beberapa struct di dalamnya, masing-masing di file itu sendiri." yang berarti Anda menginginkan setiap definisi struct dalam filenya sendiri.
BurntSushi5
1
Ini tidak akan dianggap kasar, meskipun sistem modul pasti mengizinkan penataan seperti itu. Secara umum lebih disukai untuk jalur modul untuk secara langsung berhubungan dengan jalur sistem file, misalnya struct foo::bar::Bazharus didefinisikan di foo/bar.rsatau foo/bar/mod.rs.
Chris Morgan

Jawaban:

111

Sistem modul Rust sebenarnya sangat fleksibel dan akan membiarkan Anda mengekspos jenis struktur apa pun yang Anda inginkan sambil menyembunyikan bagaimana kode Anda disusun dalam file.

Saya pikir kuncinya di sini adalah memanfaatkan pub use, yang akan memungkinkan Anda mengekspor ulang pengenal dari modul lain. Ada preseden untuk ini di std::iopeti Rust di mana beberapa jenis dari sub-modul diekspor kembali untuk digunakan distd::io .

Sunting (2019-08-25): bagian jawaban berikut ditulis beberapa waktu yang lalu. Ini menjelaskan bagaimana mengatur struktur modul seperti itu dengan rustcsendiri. Saat ini, orang biasanya akan menggunakan Cargo untuk sebagian besar kasus penggunaan. Meskipun berikut ini masih berlaku, beberapa bagiannya (mis. #![crate_type = ...]) Mungkin tampak aneh. Ini bukan solusi yang disarankan.

Untuk mengadaptasi contoh Anda, kita bisa mulai dengan struktur direktori ini:

src/
  lib.rs
  vector.rs
main.rs

Ini milik Anda main.rs:

extern crate math;

use math::vector;

fn main() {
    println!("{:?}", vector::VectorA::new());
    println!("{:?}", vector::VectorB::new());
}

Dan Anda src/lib.rs:

#[crate_id = "math"];
#[crate_type = "lib"];

pub mod vector; // exports the module defined in vector.rs

Dan akhirnya, src/vector.rs:

// exports identifiers from private sub-modules in the current
// module namespace
pub use self::vector_a::VectorA;
pub use self::vector_b::VectorB;

mod vector_b; // private sub-module defined in vector_b.rs

mod vector_a { // private sub-module defined in place
    #[derive(Debug)]
    pub struct VectorA {
        xs: Vec<i64>,
    }

    impl VectorA {
        pub fn new() -> VectorA {
            VectorA { xs: vec![] }
        }
    }
}

Dan di sinilah keajaiban terjadi. Kami telah mendefinisikan sub-modul math::vector::vector_ayang memiliki beberapa implementasi dari jenis vektor khusus. Tapi kami tidak ingin klien perpustakaan Anda peduli bahwa ada vector_asub-modul. Sebagai gantinya, kami ingin membuatnya tersedia di math::vectormodul. Ini dilakukan dengan pub use self::vector_a::VectorA, yang mengekspor ulang vector_a::VectorApengenal di modul saat ini.

Tetapi Anda bertanya bagaimana melakukan ini sehingga Anda dapat meletakkan implementasi vektor khusus Anda di file yang berbeda. Inilah yang mod vector_b;garis itu lakukan. Ini menginstruksikan kompiler Rust untuk mencari vector_b.rsfile untuk implementasi modul itu. Dan tentu saja, inilah src/vector_b.rsfile kami :

#[derive(Debug)]
pub struct VectorB {
    xs: Vec<i64>,
}

impl VectorB {
    pub fn new() -> VectorB {
        VectorB { xs: vec![] }
    }
}

Dari perspektif klien, fakta bahwa VectorAdan VectorBdidefinisikan dalam dua modul berbeda dalam dua file berbeda benar-benar tidak jelas.

Jika Anda berada di direktori yang sama dengan main.rs, Anda harus dapat menjalankannya dengan:

rustc src/lib.rs
rustc -L . main.rs
./main

Secara umum, chapter "Crates and Modules" di buku Rust cukup bagus. Ada banyak contoh.

Akhirnya, kompilator Rust juga mencari di sub-direktori untuk Anda secara otomatis. Misalnya, kode di atas tidak akan berfungsi dengan struktur direktori ini:

src/
  lib.rs
  vector/
      mod.rs
      vector_b.rs
main.rs

Perintah untuk mengkompilasi dan menjalankan juga tetap sama.

BurntSushi5
sumber
Saya yakin Anda salah paham tentang apa yang saya maksud dengan "vektor". Saya berbicara tentang vektor dalam besaran matematis , bukan struktur datanya. Juga, saya tidak menjalankan versi karat terbaru, karena agak merepotkan untuk membangun di jendela.
starscape
+1 Tidak persis seperti yang saya butuhkan, tetapi mengarahkan saya ke arah yang benar.
starscape
@EpicPineapple Memang! Dan Vec dapat digunakan untuk merepresentasikan vektor tersebut. (Untuk N yang lebih besar, tentu saja.)
BurntSushi5
1
@EpicPineapple Bisakah Anda menjelaskan apa yang terlewat dari jawaban saya sehingga saya dapat memperbaruinya? Saya berjuang untuk melihat perbedaan antara jawaban Anda dan saya selain menggunakan, math::Vec2bukan math::vector::Vec2. (yaitu, konsep yang sama tetapi satu modul lebih dalam.)
BurntSushi5
1
Saya tidak melihat kriteria itu dalam pertanyaan Anda. Sejauh yang saya bisa lihat, saya telah menjawab pertanyaan yang diajukan. (Yang benar-benar menanyakan cara memisahkan modul dari file.) Maaf tentang hal itu tidak berfungsi pada Rust 0.9, tetapi itu datang dengan wilayah menggunakan bahasa yang tidak stabil.
BurntSushi5
39

Aturan modul Rust adalah:

  1. File sumber hanyalah modulnya sendiri (kecuali file khusus main.rs, lib.rs dan mod.rs).
  2. Direktori hanyalah komponen jalur modul.
  3. File mod.rs hanyalah modul direktori.

File matrix.rs 1 dalam direktori math hanyalah modul math::matrix. Mudah. Apa yang Anda lihat di sistem file Anda juga ditemukan di kode sumber Anda. Ini adalah korespondensi satu-ke-satu dari jalur file dan jalur modul 2 .

Jadi Anda dapat mengimpor struct Matrixdengan use math::matrix::Matrix, karena struct tersebut berada di dalam file matrix.rs dalam direktori math. Tidak senang? Anda lebih suka use math::Matrix;sebaliknya, bukan? Itu mungkin. Ekspor ulang pengenal math::matrix::Matrixdi math / mod.rs dengan:

pub use self::math::Matrix;

Ada langkah lain untuk membuatnya berfungsi. Rust membutuhkan deklarasi modul untuk memuat modul. Tambahkan mod math;di main.rs. Jika Anda tidak melakukan itu, Anda mendapatkan pesan kesalahan dari kompilator saat mengimpor seperti ini:

error: unresolved import `math::Matrix`. Maybe a missing `extern crate math`?

Petunjuknya menyesatkan di sini. Tidak perlu peti tambahan, kecuali tentu saja Anda benar-benar bermaksud untuk menulis perpustakaan terpisah.

Tambahkan ini di bagian atas main.rs:

mod math;
pub use math::Matrix;

Deklarasi modul juga diperlukan untuk submodul vector, matrixdan complex, karena mathperlu memuatnya untuk mengekspornya kembali. Ekspor ulang pengenal hanya berfungsi jika Anda telah memuat modul pengenal. Artinya, untuk mengekspor ulang pengenal math::matrix::Matrixyang perlu Anda tulis mod matrix;. Anda bisa melakukannya di math / mod.rs. Oleh karena itu buatlah file dengan konten ini:

mod vector;
pub use self::vector::Vector;

mod matrix;
pub use self::matrix::Matrix;

mod complex;
pub use self::complex::Complex;

Aaa dan kamu selesai.


1 Nama file sumber biasanya dimulai dengan huruf kecil di Rust. Itulah mengapa saya menggunakan matrix.rs dan bukan Matrix.rs.

2 Java berbeda. Anda juga mendeklarasikan jalan dengan package. Itu berlebihan. Path sudah terbukti dari lokasi file sumber di sistem file. Mengapa mengulangi informasi ini dalam pernyataan di bagian atas file? Tentu saja terkadang lebih mudah untuk melihat sekilas kode sumber daripada mencari tahu lokasi sistem file dari file tersebut. Saya dapat memahami orang-orang yang mengatakan itu tidak terlalu membingungkan.

nalply
sumber
24

Rusts purists mungkin akan menyebut saya sesat dan membenci solusi ini, tapi ini jauh lebih sederhana: cukup lakukan setiap hal dalam file sendiri, lalu gunakan makro " include! " Di mod.rs:

include!("math/Matrix.rs");
include!("math/Vector.rs");
include!("math/Complex.rs");

Dengan begitu, Anda tidak mendapatkan modul bersarang tambahan, dan menghindari aturan ekspor dan penulisan ulang yang rumit. Sederhana, efektif, tidak repot.

hasvn
sumber
1
Anda baru saja membuang namespacing. Mengubah satu file dengan cara yang tidak terkait satu sama lain sekarang dapat merusak file lain. Penggunaan 'penggunaan' Anda menjadi bocor (artinya semuanya seperti use super::*). Anda tidak dapat menyembunyikan kode dari file lain (yang penting untuk abstraksi aman yang tidak aman digunakan)
Demur Rumed
12
Ya, tapi itulah yang saya inginkan dalam kasus itu: memiliki beberapa file yang berperilaku hanya satu untuk tujuan namespacing. Saya tidak menganjurkan ini untuk setiap kasus, tetapi ini adalah solusi yang berguna jika Anda tidak ingin berurusan dengan metode "satu modul per file", untuk alasan apa pun.
hasvn
Ini bagus, saya memiliki bagian dari modul saya yang hanya internal tetapi mandiri, dan ini berhasil. Saya akan mencoba untuk mendapatkan solusi modul yang tepat untuk bekerja juga, tetapi itu tidak semudah itu.
rjh
6
Saya tidak peduli disebut sesat, solusi Anda nyaman!
sailfish009
21

Baiklah, lawan kompiler saya untuk sementara waktu dan akhirnya berhasil (terima kasih kepada BurntSushi untuk menunjukkannya pub use.

main.rs:

use math::Vec2;
mod math;

fn main() {
  let a = Vec2{x: 10.0, y: 10.0};
  let b = Vec2{x: 20.0, y: 20.0};
}

math / mod.rs:

pub use self::vector::Vec2;
mod vector;

matematika / vektor.rs

use std::num::sqrt;

pub struct Vec2 {
  x: f64,
  y: f64
}

impl Vec2 {
  pub fn len(&self) -> f64 {
    sqrt(self.x * self.x + self.y * self.y) 
  }

  // other methods...
}

Struct lain dapat ditambahkan dengan cara yang sama. CATATAN: dikompilasi dengan 0.9, bukan master.

starscape
sumber
4
Perhatikan bahwa penggunaan mod math;di main.rspasangan Anda mainProgram dengan perpustakaan Anda. Jika Anda ingin mathmodul Anda independen, Anda harus mengkompilasinya secara terpisah dan menautkannya dengan extern crate math(seperti yang ditunjukkan dalam jawaban saya). Di Rust 0.9, mungkin saja sintaksnya extern mod math.
BurntSushi5
20
Akan sangat adil untuk menandai jawaban BurntSushi5 sebagai jawaban yang benar.
IluTov
2
@NSAddict Tidak. Untuk memisahkan modul dari file, Anda tidak perlu membuat peti terpisah. Itu terlalu direkayasa.
nalply
1
Mengapa ini bukan jawaban yang paling banyak dipilih ?? Pertanyaannya menanyakan bagaimana memecah proyek menjadi beberapa file, yang sesederhana jawaban ini, bukan bagaimana membaginya menjadi peti, mana yang lebih sulit dan apa yang dijawab oleh @ BurntSushi5 (mungkin pertanyaannya telah diedit?). ..
Renato
6
Jawaban @ BurntSushi5 seharusnya merupakan jawaban yang diterima. Secara sosial canggung dan bahkan mungkin bermaksud untuk mengajukan pertanyaan, mendapatkan jawaban yang sangat bagus, lalu meringkasnya sebagai jawaban terpisah dan menandai ringkasan Anda sebagai jawaban yang diterima.
hasanyasin
4

Saya ingin menambahkan di sini bagaimana Anda menyertakan file Rust ketika mereka sangat bersarang. Saya memiliki struktur berikut:

|-----main.rs
|-----home/
|---------bathroom/
|-----------------sink.rs
|-----------------toilet.rs

Bagaimana Anda mengakses sink.rsatau toilet.rsdari main.rs?

Seperti yang disebutkan orang lain, Rust tidak memiliki pengetahuan tentang file. Sebaliknya, ia melihat semuanya sebagai modul dan submodul. Untuk mengakses file di dalam direktori kamar mandi, Anda perlu mengekspornya atau memindahkannya ke atas. Anda melakukan ini dengan menentukan nama file dengan direktori yang ingin Anda akses dan pub mod filename_inside_the_dir_without_rs_extdi dalam file.

Contoh.

// sink.rs
pub fn run() { 
    println!("Wash my hands for 20 secs!");
}

// toilet.rs
pub fn run() {
    println!("Ahhh... This is sooo relaxing.")
}
  1. Buat file bernama bathroom.rsdi dalam homedirektori:

  2. Ekspor nama file:

    // bathroom.rs
    pub mod sink;
    pub mod toilet;
  3. Buat file bernama di home.rssebelahmain.rs

  4. pub mod file bathroom.rs

    // home.rs
    pub mod bathroom;
  5. Dalam main.rs

    // main.rs
    // Note: If you mod something, you just specify the 
    // topmost module, in this case, home. 
    mod home;
    
    fn main() {
        home::bathroom::sink::run();
    }

    use pernyataan juga bisa digunakan:

    // main.rs
    // Note: If you mod something, you just specify the 
    // topmost module, in this case, home. 
    use home::bathroom::{sink, toilet};
    
    fn main() {
        sink::run();
        sink::toilet();
    }

Termasuk modul saudara lainnya (file) dalam submodul

Jika Anda ingin menggunakan sink.rsfrom toilet.rs, Anda dapat memanggil modul dengan menentukan selfatau superkata kunci.

// inside toilet.rs
use self::sink;
pub fn run() {
  sink::run();
  println!("Ahhh... This is sooo relaxing.")
}

Struktur Direktori Akhir

Anda akan berakhir dengan sesuatu seperti ini:

|-----main.rs
|-----home.rs
|-----home/
|---------bathroom.rs
|---------bathroom/
|-----------------sink.rs
|-----------------toilet.rs

Struktur di atas hanya berfungsi dengan Rust 2018 dan seterusnya. Struktur direktori berikut juga berlaku untuk tahun 2018, tetapi begitulah tahun 2015 dulu berfungsi.

|-----main.rs
|-----home/
|---------mod.rs
|---------bathroom/
|-----------------mod.rs
|-----------------sink.rs
|-----------------toilet.rs

Di mana home/mod.rssama dengan ./home.rsdan home/bathroom/mod.rssama dengan home/bathroom.rs. Rust membuat perubahan ini karena kompilator akan bingung jika Anda menyertakan file dengan nama yang sama dengan direktorinya. Versi 2018 (yang ditampilkan pertama) memperbaiki struktur itu.

Lihat repo ini untuk informasi lebih lanjut dan video YouTube ini untuk penjelasan keseluruhan.

Satu hal lagi ... hindari tanda hubung! Gunakan snake_casesebagai gantinya.

Catatan penting

Anda harus memasukkan semua file ke atas, bahkan jika file dalam tidak diperlukan oleh file tingkat atas.

Ini berarti, bahwa untuk sink.rsmenemukannya toilet.rs, Anda harus memasukkannya dengan menggunakan metode di atas hingga main.rs!

Dengan kata lain, melakukan pub mod sink;atau use self::sink; di dalam tidaktoilet.rs akan berfungsi kecuali Anda telah mengeksposnya sepenuhnya main.rs!

Oleh karena itu, selalu ingat untuk menyimpan file Anda ke atas!

Jose A
sumber
2
... Itu sangat berbelit-belit dibandingkan dengan C ++, yang mengatakan sesuatu
Joseph Garvin
1
Jawaban terbaik, terima kasih.
etech