Chia mô-đun thành nhiều tệp


102

Tôi muốn có một mô-đun với nhiều cấu trúc trong đó, mỗi cấu trúc trong tệp riêng của nó. Sử dụng một Mathmô-đun làm ví dụ:

Math/
  Vector.rs
  Matrix.rs
  Complex.rs

Tôi muốn mỗi cấu trúc nằm trong cùng một mô-đun, mà tôi sẽ sử dụng từ tệp chính của mình, như sau:

use Math::Vector;

fn main() {
  // ...
}

Tuy nhiên, hệ thống mô-đun của Rust (hơi khó hiểu khi bắt đầu) không cung cấp một cách rõ ràng để làm điều này. Nó dường như chỉ cho phép bạn có toàn bộ mô-đun của mình trong một tệp. Đây có phải là không mộc mạc? Nếu không, tôi phải làm như thế nào?


1
Tôi đã giải thích "Tôi muốn có một mô-đun có nhiều cấu trúc trong đó, mỗi cấu trúc nằm trong một tệp riêng." nghĩa là bạn muốn mỗi định nghĩa cấu trúc trong tệp riêng của nó.
BurntSushi5

1
Điều này sẽ không được coi là mộc mạc, mặc dù hệ thống mô-đun chắc chắn cho phép cấu trúc như vậy. Nói chung tốt hơn là một đường dẫn mô-đun tương ứng trực tiếp với một đường dẫn hệ thống tệp, ví dụ như cấu trúc foo::bar::Baznên được định nghĩa trong foo/bar.rshoặc foo/bar/mod.rs.
Chris Morgan,

Câu trả lời:


111

Hệ thống mô-đun của Rust thực sự cực kỳ linh hoạt và sẽ cho phép bạn hiển thị bất kỳ loại cấu trúc nào bạn muốn trong khi ẩn cách mã của bạn được cấu trúc trong tệp.

Tôi nghĩ chìa khóa ở đây là tận dụng pub use, điều này sẽ cho phép bạn xuất lại số nhận dạng từ các mô-đun khác. Đã có tiền lệ cho điều này trong std::iothùng của Rust nơi một số loại từ các mô-đun con được tái xuất để sử dụngstd::io .

Chỉnh sửa (2019-08-25): phần sau của câu trả lời đã được viết cách đây khá lâu. Nó giải thích cách thiết lập cấu trúc mô-đun như vậy với rustcmột mình. Ngày nay, người ta thường sử dụng Cargo cho hầu hết các trường hợp sử dụng. Trong khi điều sau đây vẫn còn hiệu lực, một số phần của nó (ví dụ #![crate_type = ...]) có thể có vẻ lạ. Đây không phải là giải pháp được khuyến nghị.

Để điều chỉnh ví dụ của bạn, chúng tôi có thể bắt đầu với cấu trúc thư mục này:

src/
  lib.rs
  vector.rs
main.rs

Đây là của bạn main.rs:

extern crate math;

use math::vector;

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

Và của bạn src/lib.rs:

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

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

Và cuối cùng, 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![] }
        }
    }
}

Và đây là nơi điều kỳ diệu xảy ra. Chúng tôi đã xác định một mô-đun math::vector::vector_acon có một số triển khai của một loại vectơ đặc biệt. Nhưng chúng tôi không muốn khách hàng của thư viện của bạn quan tâm rằng có một vector_amô-đun con. Thay vào đó, chúng tôi muốn cung cấp nó trong math::vectormô-đun. Điều này được thực hiện với pub use self::vector_a::VectorA, xuất lại mã vector_a::VectorAđịnh danh trong mô-đun hiện tại.

Nhưng bạn đã hỏi cách thực hiện điều này để bạn có thể đặt các triển khai vectơ đặc biệt của mình trong các tệp khác nhau. Đây là những gì mod vector_b;dòng làm. Nó hướng dẫn trình biên dịch Rust tìm kiếm một vector_b.rstệp để triển khai mô-đun đó. Và chắc chắn, đây là src/vector_b.rstệp của chúng tôi :

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

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

Từ quan điểm của khách hàng, thực tế là VectorAVectorBđược xác định trong hai mô-đun khác nhau trong hai tệp khác nhau là hoàn toàn không rõ ràng.

Nếu bạn đang ở trong cùng một thư mục với main.rs, bạn sẽ có thể chạy nó với:

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

Nhìn chung, chương "Crates and Modules" trong sách Rust khá hay. Có rất nhiều ví dụ.

Cuối cùng, trình biên dịch Rust cũng tự động tìm kiếm trong các thư mục con cho bạn. Ví dụ, đoạn mã trên sẽ hoạt động không thay đổi với cấu trúc thư mục này:

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

Các lệnh để biên dịch và chạy vẫn như cũ.


Tôi tin rằng bạn đã hiểu sai ý của tôi về "vector". Tôi đang nói về vectơ như trong đại lượng toán học , không phải cấu trúc dữ liệu. Ngoài ra, tôi không chạy phiên bản rỉ sét mới nhất, vì việc xây dựng trên windows hơi khó.
starscape,

+1 Không phải là chính xác những gì tôi cần, nhưng đã chỉ cho tôi đi đúng hướng.
starscape

@EpicPineapple Thật vậy! Và một Vec có thể được sử dụng để biểu diễn các vectơ như vậy. (Tất nhiên là đối với N lớn hơn.)
BurntSushi

1
@EpicPineapple Bạn có thể giải thích câu trả lời của tôi đã bỏ sót điều gì để tôi có thể cập nhật nó không? Tôi đang đấu tranh để thấy sự khác biệt giữa câu trả lời của bạn và của tôi ngoài việc sử dụng math::Vec2thay vì math::vector::Vec2. (tức là, Cùng một khái niệm nhưng sâu hơn một mô-đun.)
BurntSushi

1
Tôi không thấy tiêu chí đó trong câu hỏi của bạn. Theo như tôi thấy, tôi đã trả lời câu hỏi được hỏi. (Đó thực sự là câu hỏi làm thế nào để tách các mô-đun khỏi các tệp.) Xin lỗi vì nó không hoạt động trên Rust 0.9, nhưng điều đó đi kèm với lãnh thổ của việc sử dụng một ngôn ngữ không ổn định.
BurntSushi

38

Các quy tắc của mô-đun Rust là:

  1. Tệp nguồn chỉ là mô-đun của riêng nó (ngoại trừ các tệp đặc biệt main.rs, lib.rs và mod.rs).
  2. Thư mục chỉ là một thành phần đường dẫn mô-đun.
  3. Tập tin mod.rs chỉ là mô-đun của thư mục.

Tập tin matrix.rs 1 trong thư mục toán chỉ là mô-đun math::matrix. Dễ thôi. Những gì bạn thấy trên hệ thống tệp của mình, bạn cũng tìm thấy trong mã nguồn của mình. Đây là sự tương ứng 1-1 của đường dẫn tệp và đường dẫn mô-đun 2 .

Vì vậy, bạn có thể nhập một cấu trúc Matrixvới use math::matrix::Matrix, vì cấu trúc nằm bên trong tệp matrix.rs trong một phép toán thư mục. Không vui? use math::Matrix;Thay vào đó, bạn sẽ thích hơn rất nhiều, phải không? Nó có thể. Xuất lại mã định danh math::matrix::Matrixtrong math / mod.rs với:

pub use self::math::Matrix;

Có một bước khác để làm cho điều này hoạt động. Rust cần một khai báo mô-đun để tải mô-đun. Thêm một mod math;trong main.rs. Nếu bạn không làm điều đó, bạn sẽ nhận được thông báo lỗi từ trình biên dịch khi nhập như thế này:

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

Gợi ý là gây hiểu lầm ở đây. Không cần thêm thùng, tất nhiên là bạn thực sự có ý định viết một thư viện riêng.

Thêm cái này vào đầu main.rs:

mod math;
pub use math::Matrix;

Việc khai báo mô-đun cũng rất cần thiết đối với các mô-đun con vector, matrixcomplexmathcần phải tải chúng để xuất lại chúng. Việc xuất lại số nhận dạng chỉ hoạt động nếu bạn đã tải mô-đun của số nhận dạng. Điều này có nghĩa là, để xuất lại số nhận dạng math::matrix::Matrixbạn cần viết mod matrix;. Bạn có thể làm điều này trong math / mod.rs. Do đó, hãy tạo tệp với nội dung này:

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

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

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

Aaa và bạn đã hoàn thành.


1 Tên tệp nguồn thường bắt đầu bằng chữ thường trong Rust. Đó là lý do tại sao tôi sử dụng matrix.rs chứ không phải Matrix.rs.

2 Java khác nhau. Bạn cũng khai báo đường dẫn với package. Nó thừa. Đường dẫn đã được hiển thị rõ ràng từ vị trí tệp nguồn trong hệ thống tệp. Tại sao lại lặp lại thông tin này trong một khai báo ở đầu tệp? Tất nhiên, đôi khi sẽ dễ dàng hơn nếu bạn xem nhanh mã nguồn thay vì tìm ra vị trí hệ thống tệp của tệp. Tôi có thể hiểu những người nói rằng nó ít khó hiểu hơn.


23

Những người theo chủ nghĩa thuần túy Rusts có thể sẽ gọi tôi là kẻ dị giáo và ghét giải pháp này, nhưng điều này đơn giản hơn nhiều: chỉ cần thực hiện từng việc trong tệp của chính nó, sau đó sử dụng macro " include! " Trong mod.rs:

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

Bằng cách đó, bạn không nhận được thêm các mô-đun lồng nhau và tránh các quy tắc xuất và viết lại phức tạp. Đơn giản, hiệu quả, không cầu kỳ.


1
Bạn vừa ném ra không gian tên. Thay đổi một tệp theo cách không liên quan đến tệp khác giờ đây có thể phá vỡ các tệp khác. Việc bạn 'sử dụng' trở nên rò rỉ (tức là mọi thứ đều như ý use super::*). Bạn không thể che giấu mã từ file khác (đó là quan trọng đối với an toàn-sử dụng trừu tượng an toàn)
ngán ngại Rumed

11
Đúng, nhưng đó chính xác là những gì tôi muốn trong trường hợp đó: có một số tệp hoạt động như một tệp cho mục đích không gian tên. Tôi không ủng hộ điều này cho mọi trường hợp, nhưng đó là một giải pháp hữu ích nếu bạn không muốn đối phó với phương pháp "một mô-đun cho mỗi tệp", vì bất kỳ lý do gì.
hasvn

Điều này thật tuyệt, tôi có một phần của mô-đun của mình chỉ là nội bộ nhưng khép kín, và điều này đã thực hiện được một mẹo nhỏ. Tôi cũng sẽ cố gắng làm cho giải pháp mô-đun thích hợp hoạt động, nhưng không phải nơi nào cũng dễ dàng.
rjh

5
Tôi không quan tâm bị gọi là dị giáo, giải pháp của bạn rất tiện lợi!
sailfish009

20

Được rồi, đã chiến đấu với trình biên dịch của tôi một lúc và cuối cùng nó đã hoạt động (cảm ơn BurntSushi đã chỉ ra 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};
}

toán học / mod.rs:

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

toán / vector.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...
}

Các cấu trúc khác có thể được thêm vào theo cách tương tự. LƯU Ý: biên dịch với 0.9, không phải master.


4
Lưu ý rằng việc bạn sử dụng mod math;trong main.rscặp mainchương trình của bạn với thư viện của bạn. Nếu bạn muốn mathmô-đun của mình độc lập, bạn sẽ cần phải biên dịch riêng và liên kết với nó bằng extern crate math(như trong câu trả lời của tôi). Trong Rust 0.9, có thể là cú pháp extern mod math.
BurntSushi5

20
Thực sự sẽ rất công bằng nếu đánh dấu câu trả lời của BurntSushi5 là câu trả lời đúng.
IluTov

2
@NSAddict Không. Để tách các mô-đun khỏi tệp, bạn không cần tạo thùng riêng. Nó được thiết kế quá mức.
nalply

1
Tại sao đây không phải là câu trả lời được bình chọn nhiều nhất ?? Câu hỏi đặt ra là làm thế nào để chia nhỏ dự án thành một vài tệp, điều này đơn giản như câu trả lời này cho thấy, không phải làm thế nào để chia nó thành các thùng, câu hỏi này khó hơn và @ BurntSushi5 đã trả lời (có thể câu hỏi đã được chỉnh sửa?). ..
Renato

6
Câu trả lời của @ BurntSushi5 lẽ ra phải là câu trả lời được chấp nhận. Thật là khó xử về mặt xã hội và thậm chí có thể có nghĩa là đặt một câu hỏi, nhận được một câu trả lời rất hay, sau đó tóm tắt nó thành một câu trả lời riêng biệt và đánh dấu phần tóm tắt của bạn là câu trả lời được chấp nhận.
hasanyasin

3

Tôi muốn thêm vào đây cách bạn bao gồm các tệp Rust khi chúng được lồng sâu vào nhau. Tôi có cấu trúc sau:

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

Làm thế nào để bạn truy cập sink.rshoặc toilet.rstừ main.rs?

Như những người khác đã đề cập, Rust không có kiến ​​thức về tệp. Thay vào đó, nó coi mọi thứ là mô-đun và mô-đun con. Để truy cập các tệp bên trong thư mục phòng tắm, bạn cần xuất chúng hoặc chuyển chúng lên đầu. Bạn thực hiện việc này bằng cách chỉ định tên tệp với thư mục bạn muốn truy cập và pub mod filename_inside_the_dir_without_rs_extbên trong tệp.

Thí dụ.

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

// toilet.rs
pub fn run() {
    println!("Ahhh... This is sooo relaxing.")
}
  1. Tạo một tệp có tên bathroom.rsbên trong homethư mục:

  2. Xuất các tên tệp:

    // bathroom.rs
    pub mod sink;
    pub mod toilet;
    
  3. Tạo một tệp có tên home.rsbên cạnhmain.rs

  4. pub mod tệp bath.rs

    // home.rs
    pub mod bathroom;
    
  5. Trong 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 câu lệnh cũng có thể được sử dụng:

    // 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();
    }
    

Bao gồm các mô-đun anh em khác (tệp) trong mô-đun con

Trong trường hợp bạn muốn sử dụng sink.rstừ đó toilet.rs, bạn có thể gọi mô-đun bằng cách chỉ định selfhoặc supertừ khóa.

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

Cấu trúc thư mục cuối cùng

Bạn sẽ kết thúc với một cái gì đó như thế này:

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

Cấu trúc trên chỉ hoạt động với Rust 2018 trở đi. Cấu trúc thư mục sau đây cũng hợp lệ cho năm 2018, nhưng đó là cách năm 2015 hoạt động.

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

Trong đó home/mod.rslà giống như ./home.rshome/bathroom/mod.rsgiống như home/bathroom.rs. Rust đã thực hiện thay đổi này vì trình biên dịch sẽ bị nhầm lẫn nếu bạn đưa vào một tệp có cùng tên với thư mục. Phiên bản 2018 (phiên bản được hiển thị đầu tiên) sửa lỗi cấu trúc đó.

Xem repo này để biết thêm thông tin và video YouTube này để biết giải thích tổng thể.

Một điều cuối cùng ... hãy tránh dấu gạch ngang! Sử dụng snake_casethay thế.

Lưu ý quan trọng

Bạn phải chuyển tất cả các tệp lên đầu, ngay cả khi các tệp sâu không được yêu cầu bởi những tệp cấp cao nhất.

Điều này có nghĩa là, sink.rsđể khám phá toilet.rs, bạn cần phải đóng gói chúng bằng cách sử dụng các phương pháp trên hết main.rs!

Nói cách khác, thực hiện pub mod sink;hoặc use self::sink; bên trong toilet.rssẽ không hoạt động trừ khi bạn đã tiếp xúc với chúng đến cùng main.rs!

Do đó, hãy luôn nhớ xếp các tệp của bạn lên đầu!


2
... điều đó thật phức tạp so với C ++, đang nói lên điều gì đó
Joseph Garvin
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.