แบ่งโมดูลออกเป็นหลาย ๆ ไฟล์


104

ฉันต้องการมีโมดูลที่มีโครงสร้างหลายชุดแต่ละโมดูลอยู่ในไฟล์ของตัวเอง ใช้Mathโมดูลเป็นตัวอย่าง:

Math/
  Vector.rs
  Matrix.rs
  Complex.rs

ฉันต้องการให้โครงสร้างแต่ละตัวอยู่ในโมดูลเดียวกันซึ่งฉันจะใช้จากไฟล์หลักของฉันดังนี้:

use Math::Vector;

fn main() {
  // ...
}

อย่างไรก็ตามระบบโมดูลของ Rust (ซึ่งค่อนข้างสับสนในการเริ่มต้น) ไม่ได้ให้วิธีที่ชัดเจนในการทำเช่นนี้ ดูเหมือนว่าจะอนุญาตให้คุณมีโมดูลทั้งหมดในไฟล์เดียวเท่านั้น นี่คือบ้านนอก? ถ้าไม่ฉันจะทำอย่างไร


1
ฉันตีความว่า "ฉันต้องการมีโมดูลที่มีโครงสร้างหลายชุดอยู่ในไฟล์ของตัวเอง" หมายความว่าคุณต้องการคำจำกัดความของโครงสร้างแต่ละรายการในไฟล์ของตัวเอง
BurntSushi5

1
สิ่งนี้จะไม่ถือว่าเป็นเรื่องธรรมดาแม้ว่าระบบโมดูลจะอนุญาตให้มีโครงสร้างดังกล่าวอย่างแน่นอน มันเป็นเรื่องปกติที่นิยมสำหรับเส้นทางโมดูลเพื่อสอดคล้องโดยตรงกับเส้นทางของระบบไฟล์เช่น struct foo::bar::Bazควรจะกำหนดไว้ในหรือfoo/bar.rs foo/bar/mod.rs
Chris Morgan

คำตอบ:


115

ระบบโมดูลของ Rust มีความยืดหยุ่นอย่างไม่น่าเชื่อและจะช่วยให้คุณสามารถเปิดเผยโครงสร้างแบบใดก็ได้ที่คุณต้องการในขณะที่ซ่อนว่าโค้ดของคุณมีโครงสร้างอย่างไรในไฟล์

ฉันคิดว่ากุญแจสำคัญในที่นี้คือการใช้ประโยชน์pub useซึ่งจะช่วยให้คุณสามารถส่งออกตัวระบุจากโมดูลอื่นได้อีกครั้ง มีแบบอย่างสำหรับสิ่งนี้ในstd::ioลังของ Rust ซึ่งบางประเภทจากโมดูลย่อยจะถูกส่งออกอีกครั้งเพื่อใช้ในstd::io .

แก้ไข (2019-08-25): คำตอบต่อไปนี้เขียนไว้เมื่อไม่นานมานี้ อธิบายวิธีการตั้งค่าโครงสร้างโมดูลด้วยตัวrustcคนเดียว ทุกวันนี้ใคร ๆ ก็มักจะใช้ Cargo เป็นส่วนใหญ่ แม้ว่าสิ่งต่อไปนี้จะยังใช้ได้ แต่บางส่วน (เช่น#![crate_type = ...]) อาจดูแปลก ๆ นี่ไม่ใช่วิธีแก้ปัญหาที่แนะนำ

ในการปรับตัวอย่างของคุณเราสามารถเริ่มต้นด้วยโครงสร้างไดเร็กทอรีนี้:

src/
  lib.rs
  vector.rs
main.rs

นี่คือmain.rs:

extern crate math;

use math::vector;

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

และของคุณsrc/lib.rs:

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

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

และสุดท้าย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![] }
        }
    }
}

และนี่คือจุดที่ความมหัศจรรย์เกิดขึ้น เราได้กำหนดโมดูลย่อยmath::vector::vector_aซึ่งมีการนำเวกเตอร์ชนิดพิเศษมาใช้ แต่เราไม่ต้องการให้ไคลเอ็นต์ของไลบรารีของคุณสนใจว่ามีvector_aโมดูลย่อยอยู่ แต่เราต้องการทำให้พร้อมใช้งานในmath::vectorโมดูล สิ่งนี้ทำได้โดยpub use self::vector_a::VectorAส่งออกvector_a::VectorAตัวระบุในโมดูลปัจจุบันอีกครั้ง

แต่คุณถามว่าจะทำอย่างไรเพื่อให้คุณสามารถใช้งานเวกเตอร์พิเศษของคุณในไฟล์ต่างๆได้ นี่คือสิ่งที่mod vector_b;ไลน์ทำ มันสั่งให้คอมไพเลอร์ Rust ค้นหาvector_b.rsไฟล์สำหรับการใช้งานโมดูลนั้น และนี่คือsrc/vector_b.rsไฟล์ของเรา:

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

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

จากมุมมองของลูกค้าความจริงที่ว่าVectorAและVectorBถูกกำหนดไว้ในโมดูลที่แตกต่างกันสองโมดูลในสองไฟล์ที่แตกต่างกันนั้นทึบทั้งหมด

หากคุณอยู่ในไดเร็กทอรีเดียวกับmain.rsคุณควรจะสามารถเรียกใช้ด้วย:

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

โดยทั่วไปบท"Crates and Modules"ในหนังสือ Rust นั้นค่อนข้างดี มีตัวอย่างมากมาย

สุดท้ายคอมไพเลอร์ Rust ยังค้นหาในไดเรกทอรีย่อยให้คุณโดยอัตโนมัติ ตัวอย่างเช่นโค้ดด้านบนจะทำงานโดยไม่เปลี่ยนแปลงกับโครงสร้างไดเร็กทอรีนี้:

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

คำสั่งในการคอมไพล์และรันยังคงเหมือนเดิมเช่นกัน


ฉันเชื่อว่าคุณเข้าใจผิดว่า "เวกเตอร์" หมายถึงอะไร ฉันกำลังพูดถึงเวกเตอร์ในปริมาณทางคณิตศาสตร์ไม่ใช่โครงสร้างข้อมูล นอกจากนี้ฉันไม่ได้ใช้สนิมรุ่นล่าสุดเพราะการสร้างบน windows นั้นค่อนข้างเจ็บปวด
starscape

+1 ไม่ใช่สิ่งที่ฉันต้องการ แต่ชี้ให้ฉันเห็นในทิศทางที่ถูกต้อง
starscape

@EpicPineapple แน่นอน! และ Vec สามารถใช้แทนเวกเตอร์ดังกล่าวได้ (สำหรับ N ที่ใหญ่กว่าแน่นอน)
BurntSushi5

1
@EpicPineapple คุณช่วยอธิบายได้ไหมว่าคำตอบของฉันพลาดอะไรไปเพื่อที่ฉันจะได้อัปเดต ฉันดิ้นรนที่จะเห็นความแตกต่างระหว่างคำตอบและเหมืองอื่น ๆ กว่าการใช้ของคุณแทนmath::Vec2 math::vector::Vec2(กล่าวคือแนวคิดเดียวกัน แต่โมดูลหนึ่งลึกกว่า)
BurntSushi5

1
ฉันไม่เห็นเกณฑ์นั้นในคำถามของคุณ เท่าที่ฉันเห็นฉันได้ตอบคำถามที่ถามแล้ว (ซึ่งขอวิธีการแยกโมดูลออกจากไฟล์จริงๆ) ขออภัยที่มันไม่ทำงานบน Rust 0.9 แต่มาพร้อมกับอาณาเขตของการใช้ภาษาที่ไม่เสถียร
BurntSushi5

45

กฎของโมดูลสนิมคือ:

  1. ซอร์สไฟล์เป็นเพียงโมดูลของตัวเอง (ยกเว้นไฟล์พิเศษ main.rs, lib.rs และ mod.rs)
  2. ไดเร็กทอรีเป็นเพียงส่วนประกอบพา ธ โมดูล
  3. ไฟล์ mod.rs เป็นเพียงโมดูลของไดเร็กทอรี

matrix.rs ไฟล์1ในวิชาคณิตศาสตร์ไดเรกทอรีเป็นเพียงmath::matrixโมดูล มันเป็นเรื่องง่าย. สิ่งที่คุณเห็นในระบบไฟล์ของคุณคุณจะพบในซอร์สโค้ดของคุณด้วย นี่เป็นจดหมายแบบหนึ่งต่อหนึ่งในเส้นทางแฟ้มและเส้นทางโมดูล2

ดังนั้นคุณจึงสามารถอิมพอร์ต Struct Matrixด้วยuse math::matrix::Matrixเนื่องจาก struct อยู่ภายในไฟล์ matrix.rs ในไดเร็กทอรีคณิต ไม่ได้มีความสุข? คุณต้องการuse math::Matrix;มากแทนใช่ไหม มันเป็นไปได้. ส่งออกตัวระบุอีกครั้งmath::matrix::Matrixใน math / mod.rs ด้วย:

pub use self::math::Matrix;

มีขั้นตอนอื่นในการทำงานนี้ สนิมต้องการการประกาศโมดูลเพื่อโหลดโมดูล เพิ่มmod math;ใน main.rs หากคุณไม่ทำเช่นนั้นคุณจะได้รับข้อความแสดงข้อผิดพลาดจากคอมไพเลอร์เมื่อนำเข้าดังนี้:

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

คำใบ้ที่นี่ทำให้เข้าใจผิด ไม่จำเป็นต้องมีลังเพิ่มเติมยกเว้นแน่นอนว่าคุณตั้งใจจะเขียนห้องสมุดแยกต่างหาก

เพิ่มที่ด้านบนของ main.rs:

mod math;
pub use math::Matrix;

ประกาศโมดูลยังเป็นสิ่งจำเป็นสำหรับ submodules vector, matrixและcomplexเพราะmathความต้องการที่จะโหลดพวกเขาเพื่อการส่งออกใหม่พวกเขา การส่งออกตัวระบุซ้ำจะใช้ได้เฉพาะเมื่อคุณโหลดโมดูลของตัวระบุ ซึ่งหมายความว่าในการส่งออกตัวระบุที่math::matrix::Matrixคุณต้องเขียนmod matrix;อีกครั้ง คุณสามารถทำได้ใน math / mod.rs ดังนั้นสร้างไฟล์ด้วยเนื้อหานี้:

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

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

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

และคุณทำเสร็จแล้ว


1ชื่อไฟล์ต้นฉบับมักขึ้นต้นด้วยอักษรตัวพิมพ์เล็กใน Rust นั่นเป็นเหตุผลที่ฉันใช้ matrix.rs ไม่ใช่ Matrix.rs

2ของ Java แตกต่างกัน คุณประกาศเส้นทางด้วยpackageเช่นกัน มันซ้ำซ้อน พา ธ นั้นเห็นได้ชัดจากตำแหน่งไฟล์ต้นทางในระบบไฟล์ ทำไมต้องทำซ้ำข้อมูลนี้ในการประกาศที่ด้านบนของไฟล์ แน่นอนว่าบางครั้งการตรวจดูซอร์สโค้ดอย่างรวดเร็วแทนที่จะค้นหาตำแหน่งระบบไฟล์ของไฟล์นั้นง่ายกว่า ฉันเข้าใจคนที่บอกว่ามันสับสนน้อยลง


25

คนเจ้าระเบียบสนิมอาจเรียกฉันว่าเป็นคนนอกรีตและเกลียดวิธีนี้ แต่วิธีนี้ง่ายกว่ามากแค่ทำแต่ละอย่างในไฟล์ของตัวเองจากนั้นใช้มาโคร" รวม! " ใน mod.rs:

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

ด้วยวิธีนี้คุณจะไม่ได้รับโมดูลที่ซ้อนกันเพิ่มและหลีกเลี่ยงการส่งออกและเขียนกฎที่ซับซ้อน เรียบง่ายมีประสิทธิภาพไม่ยุ่งยาก


1
คุณเพิ่งโยนเนมสเปซออกไป การเปลี่ยนไฟล์หนึ่งไฟล์ด้วยวิธีที่ไม่เกี่ยวข้องกับอีกไฟล์สามารถทำลายไฟล์อื่นได้แล้ว การใช้ 'การใช้งาน' ของคุณมีการรั่วไหล (กล่าวคือทุกอย่างเป็นเช่นนั้นuse super::*) คุณไม่สามารถซ่อนโค้ดจากไฟล์อื่น ๆ ได้ (ซึ่งเป็นสิ่งสำคัญสำหรับการใช้นามธรรมที่ปลอดภัยอย่างไม่ปลอดภัย)
Demur Rumed

13
ใช่ แต่นั่นคือสิ่งที่ฉันต้องการในกรณีนั้น: มีไฟล์หลายไฟล์ที่ทำงานเป็นเพียงไฟล์เดียวเพื่อวัตถุประสงค์ในการตั้งชื่อ ฉันไม่สนับสนุนสิ่งนี้ในทุกกรณี แต่เป็นวิธีแก้ปัญหาที่มีประโยชน์หากคุณไม่ต้องการจัดการกับวิธี "หนึ่งโมดูลต่อไฟล์" ไม่ว่าด้วยเหตุผลใดก็ตาม
hasvn

นี่เยี่ยมมากฉันมีส่วนหนึ่งของโมดูลของฉันที่เป็นแบบภายในเท่านั้น แต่มีอยู่ในตัวและนี่ก็เป็นเคล็ดลับ ฉันจะพยายามทำให้โซลูชันโมดูลที่เหมาะสมใช้งานได้ แต่ก็ไม่ง่ายอย่างที่คิด
rjh

7
ฉันไม่สนใจว่าจะถูกเรียกว่านอกรีตทางออกของคุณสะดวก!
sailfish009

21

Alright ต่อสู้คอมไพเลอร์ของฉันในขณะที่และในที่สุดได้รับมันในการทำงาน (ขอบคุณ BurntSushi 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};
}

คณิตศาสตร์ / mod.rs:

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

คณิตศาสตร์ / 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...
}

สามารถเพิ่มโครงสร้างอื่น ๆ ในลักษณะเดียวกันได้ หมายเหตุ: คอมไพล์ด้วย 0.9 ไม่ใช่มาสเตอร์


4
โปรดทราบว่าการใช้งานของmod math;ในmain.rsคู่ของคุณmainโปรแกรมห้องสมุดของคุณ หากคุณต้องการให้mathโมดูลของคุณเป็นอิสระคุณจะต้องรวบรวมแยกต่างหากและเชื่อมโยงกับโมดูลextern crate math(ดังแสดงในคำตอบของฉัน) ใน Rust 0.9 เป็นไปได้ว่าไวยากรณ์จะถูกextern mod mathแทนที่
BurntSushi5

20
การทำเครื่องหมายคำตอบของ BurntSushi5 เป็นคำตอบที่ถูกต้องเป็นเรื่องยุติธรรม
IluTov

2
@NSAddict ไม่หากต้องการหย่าโมดูลจากไฟล์คุณไม่จำเป็นต้องสร้างลังแยกต่างหาก มันถูกออกแบบมามากเกินไป
nalply

1
ทำไมถึงไม่เป็นคำตอบที่ได้รับคะแนนโหวตสูงสุด ?? คำถามถามว่าจะแบ่งโปรเจ็กต์ออกเป็นไฟล์สองสามไฟล์ได้อย่างไรซึ่งง่าย ๆ อย่างที่คำตอบนี้แสดงไม่ใช่วิธีแยกเป็นลังซึ่งยากกว่าและเป็นสิ่งที่ @ BurntSushi5 ตอบ (คำถามอาจถูกแก้ไขหรือไม่) ..
Renato

6
คำตอบของ @ BurntSushi5 น่าจะเป็นคำตอบที่ยอมรับได้ เป็นเรื่องที่น่าอึดอัดทางสังคมและอาจหมายถึงการถามคำถามรับคำตอบที่ดีมากจากนั้นสรุปเป็นคำตอบแยกต่างหากและทำเครื่องหมายสรุปของคุณเป็นคำตอบที่ยอมรับ
hasanyasin

5

ฉันต้องการเพิ่มที่นี่ว่าคุณรวมไฟล์ Rust อย่างไรเมื่อไฟล์เหล่านี้ซ้อนกันอย่างลึกซึ้ง ฉันมีโครงสร้างดังต่อไปนี้:

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

คุณเข้าถึงsink.rsหรือtoilet.rsจากmain.rsอย่างไร

ดังที่คนอื่น ๆ พูดถึง Rust ไม่มีความรู้เกี่ยวกับไฟล์ แต่จะเห็นทุกอย่างเป็นโมดูลและโมดูลย่อย ในการเข้าถึงไฟล์ภายในไดเร็กทอรีห้องน้ำคุณต้องส่งออกหรือส่งออกไปที่ด้านบน คุณทำได้โดยระบุชื่อไฟล์ที่มีไดเร็กทอรีที่คุณต้องการเข้าถึงและpub mod filename_inside_the_dir_without_rs_extอยู่ภายในไฟล์

ตัวอย่าง.

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

// toilet.rs
pub fn run() {
    println!("Ahhh... This is sooo relaxing.")
}
  1. สร้างไฟล์ที่เรียกว่าbathroom.rsภายในhomeไดเร็กทอรี:

  2. ส่งออกชื่อไฟล์:

    // bathroom.rs
    pub mod sink;
    pub mod toilet;
    
  3. สร้างไฟล์ชื่อhome.rsถัดจากmain.rs

  4. pub mod ไฟล์ bathroom.rs

    // home.rs
    pub mod bathroom;
    
  5. ภายใน 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 นอกจากนี้ยังสามารถใช้คำสั่ง:

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

รวมถึงโมดูลพี่น้องอื่น ๆ (ไฟล์) ภายในโมดูลย่อย

ในกรณีที่คุณต้องการใช้sink.rsจากtoilet.rsคุณสามารถเรียกโมดูลโดยการระบุselfหรือsuperคำหลัก

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

โครงสร้างไดเรกทอรีขั้นสุดท้าย

คุณจะจบลงด้วยสิ่งนี้:

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

โครงสร้างด้านบนใช้ได้กับ Rust 2018 เป็นต้นไปเท่านั้น โครงสร้างไดเร็กทอรีต่อไปนี้ใช้ได้สำหรับปี 2018 เช่นกัน แต่เป็นวิธีที่ 2015 ใช้ในการทำงาน

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

ซึ่งhome/mod.rsเป็นเช่นเดียวกับ./home.rsและเป็นเช่นเดียวกับhome/bathroom/mod.rs home/bathroom.rsRust ทำการเปลี่ยนแปลงนี้เนื่องจากคอมไพเลอร์จะสับสนหากคุณรวมไฟล์ที่มีชื่อเดียวกันกับไดเร็กทอรี เวอร์ชัน 2018 (รุ่นที่แสดงก่อน) แก้ไขโครงสร้างนั้น

ดูrepo นี้สำหรับข้อมูลเพิ่มเติมและวิดีโอ YouTubeนี้สำหรับคำอธิบายโดยรวม

สิ่งสุดท้าย ... หลีกเลี่ยงยัติภังค์! ใช้snake_caseแทน

โน๊ตสำคัญ

คุณต้องสร้างไฟล์ทั้งหมดขึ้นด้านบนแม้ว่าไฟล์ระดับบนสุดจะไม่ต้องการไฟล์ลึกก็ตาม

ซึ่งหมายความว่าเพื่อที่sink.rsจะค้นพบtoilet.rsคุณจะต้องสร้างมันขึ้นมาโดยใช้วิธีการที่กล่าวมาข้างต้นmain.rs!

กล่าวอีกนัยหนึ่งการทำpub mod sink;หรือuse self::sink; ภายในtoilet.rsจะไม่ได้ผลเว้นแต่คุณจะได้สัมผัสมันจนหมดmain.rs!

ดังนั้นอย่าลืมใส่ไฟล์ของคุณไว้ด้านบนเสมอ!


3
... นั่นคือความสับสนอย่างเมามันเมื่อเทียบกับ C ++ ซึ่งกำลังพูดอะไรบางอย่าง
โจเซฟการ์วิน
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.