Rust之模块和路径(一)

https://doc.rust-lang.org/edition-guide/rust-2018/module-system/path-clarity.html
模块在官网上被誉为新手死亡问题:

The module system is often one of the hardest things for people new to Rust. Everyone has their own things that take time to master, of course, but there’s a root cause for why it’s so confusing to many: while there are simple and consistent rules defining the module system, their consequences can feel inconsistent, counterintuitive and mysterious.

连续三天,在干了有限的论坛帖子和全部的官方资料后,终于明白了Rust模块的管理。在看本文之前,你首先要对Rust的模块管理有基本的了解:
7.使用包、Crate和模块管理不断增长的项目

此前我已经阅读了官方的两个文档(书和reference),但是在学习Diesel的过程中,注意到它用的use引入包方式不一样(后来发现是2015版本),在改成2018版本过程中,引发了一系列问题…现在解决了,所以把结论分享出来。

结论

  1. crate根路径。一个package可以有一个库,和多个二进制。一个库是一个crate,一个二进制也是一个crate(凡是没有main的都是“库的碎片”,这些碎片的总和,是一个库)。除非在Cargo.toml分别为它们命名,否则他们的名字是同一个,即Cargo.toml的package的名字。假设文件a.rs中有一行:
use crate::xxx::yyy

如果a.rs是库的碎片,crate是库的root,如果a.rs是可执行文件,crate是它自己。

  1. 什么是二进制,什么是库。二进制就是有main函数的crate,一个在src目录下,其他的在bin目录下。关于package的组织形式。库就是lib.rs,以及所有没有main的rs。我喜欢叫他们库碎片。
  2. 在二进制中引用库。有两种方式:
use self::schema::posts; // 因为Cargo.toml没有分别命名,所以二进制文件和库有同样的名字,所以self此时代表的也是库的root
use desel_demo::schema::posts; //就像第三方依赖,可以用库名访问它
  1. 进制文件引用库。假设库的名字(Cargo.toml里的name)是desel_demo,在二进制文件中使用它:
use desel_demo::xxx::yyy

这一点和第三方库一致,假设用到的第三方库是diesel,也是用:

use diesel::xxx
  1. 库的碎片引用库的碎片。分两种情况:
// 碎片a和b是姐妹,假设他们是库root的孩子——绝对路径
use crate::b
// 碎片b是a的孩子——相对路径
use b
// 碎片b是a的孙子——相对路径
use some_child::b
// 重孙子....

不管是什么情况,都不会有碎片a

use b

这是因为,能用use b的,要么b是个第三方库,要么b是自己的库(如果自己是二进制文件,并且b的名字在package的lib的name里),这点和第二点是一个意思。

在库中引用库碎片:

use self::schema::posts; // 如果当前文件是库的root,也就是lib.rs或任意二进制文件——bin/some_binary.rs或main.rs
use super::schema::posts; // 假设当前文件是库的root下面一个rs文件,比如model.rs,它的super是root,所以可以用super::schema

在库中引用库碎片不能用:

use desel_demo::xxx; // 因为desel_demo是它自己
  1. 2015和2018的差异——依赖和宏。最大的影响extern crate xxx。这个在2015里,对第三方库、第三方宏、和自己的库(root)都需要声明,在2018里不再需要,但是在2018里,它们依然可以用。但是对于2015的宏引入方式,可以一次性引入依赖的所有宏,对于宏嵌套的情况尤其好用;如果用2018的方式,需要逐个引入所用的所有宏,包括嵌套的宏。
// Rust 2015
#[macro_use]
extern crate bar;

fn main() {
    baz!();
}

// Rust 2018
use bar::baz;

fn main() {
    baz!();
}
  1. 二进制main.rs的名字,是package的名字;其他的二进制(都要放在bin下面),同各自的文件名字,如二进制文件show_posts.rs,它的应用名字就叫show_posts。
  2. 依赖的名字,除非在Cargo.toml中用[lib]name修改,否则也是package的名字。

例子

│ .env
│ Cargo.lock
│ Cargo.toml
│ diesel.toml

├─migrations
│ ├─00000000000000_diesel_initial_setup
│ │ down.sql
│ │ up.sql
│ │
│ └─20160815133237_create_posts
│ down.sql
│ up.sql

├─src
│ │ lib.rs
│ │ models.rs
│ │ schema.rs
│ │
│ └─bin
│ show_posts.rs
│ write_post.rs

└─target

lib.rs:

pub mod models;
pub mod schema;

use diesel::prelude::*;
use dotenv::dotenv;
use std::env;

#[macro_use]
extern crate diesel;

// use models::{NewPost, Post} // 方法1. 相对路径省略self
// use self::models::{NewPost, Post}; // 方法2, 相对路径
use crate::models::{NewPost, Post}; // 方法3, 绝对路径

pub fn establish_connection() -> MysqlConnection {
    dotenv().ok();

    let database_url = env::var("DATABASE_URL").expect("DATABASE_URL must be set");
    MysqlConnection::establish(&database_url)
        .unwrap_or_else(|_| panic!("Error connecting to {}", database_url))
}

pub fn create_post(conn: &MysqlConnection, title: &str, body: &str) -> Post {
    use schema::posts;

    let new_post = NewPost { title, body };

    diesel::insert_into(posts::table)
        .values(&new_post)
        .execute(conn)
        .expect("Error saving new post");

    posts::table.order(posts::id.desc()).first(conn).unwrap()
}

models.rs

use diesel::{Queryable,Insertable};
// use super::schema::posts; 方法1,相对路径
use crate::schema::posts // 方法2,绝对路径(不能用依赖名称)

#[derive(Queryable)]
pub struct Post {
    pub id: i32,
    pub title: String,
    pub body: String,
    pub published: bool,
}

#[derive(Insertable)]
#[table_name = "posts"]
pub struct NewPost<'a> {
    pub title: &'a str,
    pub body: &'a str,
}

schema.rs

// use diesel::table; 2018方式,需要引入其他的嵌套宏;
// lib.rs用了2015的#[macro_use],引入所有的diesel宏

table! {
    posts (id) {
        id -> Integer,
        title -> Varchar,
        body -> Text,
        published -> Bool,
    }
}

show_posts.rs

// use diesel_demo::models::*; 方法1,绝对路径,通过依赖名称引入
// use self::models::*; 方法2,相对路径同名依赖子模块
// use models::*; 方法3,相对路径中的self可以省略
use crate::models::*; // 方法4, 绝对路径,通过crate关键字引入
use diesel::prelude::*;
use diesel_demo::*;

fn main() {
    // use diesel_demo::schema::posts::dsl::*; // 方法1
    // use self::schema::posts::dsl::*; // 方法2
    // use schema::posts::dsl::*; // 方法3
    use crate::schema::posts::dsl::*; // 方法4
    let connection = establish_connection();
    let results = posts
        .filter(published.eq(true))
        .limit(5)
        .load::(&connection)
        .expect("Error loading posts");

    println!("Displaying {} posts", results.len());
    for post in results {
        println!("{}", post.title);
        println!("-----------\n");
        println!("{}", post.body);
    }
}

编译

Compiling diesel_api v0.1.0 (G:\workspace_rh\bbc\study2\diesel_demo)
Running rustc --crate-name diesel_api --edition=2018 src/lib.rs --error-format=json --json=diagnostic-rendered-ansi --crate-type lib --emit=dep-info,metadata,link -C embed-bitcode=no -C debuginfo=2 -C metadata=fe739cf32ff0b44a -C extra-filename=-fe739cf32ff0b44a --out-dir 'G:\workspace_rh\bbc\study2\diesel_demo\target\debug\deps' -C 'incremental=G:\workspace_rh\bbc\study2\diesel_demo\target\debug\incremental' -L 'dependency=G:\workspace_rh\bbc\study2\diesel_demo\target\debug\deps' --extern 'diesel=G:\workspace_rh\bbc\study2\diesel_demo\target\debug\deps\libdiesel-7a9409e220aa80ac.rmeta' --extern 'dotenv=G:\workspace_rh\bbc\study2\diesel_demo\target\debug\deps\libdotenv-dca3ca0087910b06.rmeta' -L 'native=C:\Program Files\MySQL\MySQL Connector C 6.1\lib\vs14'
Running rustc --crate-name diesel_api --edition=2018 'src\main.rs' --error-format=json --json=diagnostic-rendered-ansi --crate-type bin --emit=dep-info,link -C embed-bitcode=no -C debuginfo=2 -C metadata=8c1c4d3f22495238 --out-dir 'G:\workspace_rh\bbc\study2\diesel_demo\target\debug\deps' -C 'incremental=G:\workspace_rh\bbc\study2\diesel_demo\target\debug\incremental' -L 'dependency=G:\workspace_rh\bbc\study2\diesel_demo\target\debug\deps' --extern 'diesel=G:\workspace_rh\bbc\study2\diesel_demo\target\debug\deps\libdiesel-7a9409e220aa80ac.rlib' --extern 'diesel_api=G:\workspace_rh\bbc\study2\diesel_demo\target\debug\deps\libdiesel_api-fe739cf32ff0b44a.rlib' --extern 'dotenv=G:\workspace_rh\bbc\study2\diesel_demo\target\debug\deps\libdotenv-dca3ca0087910b06.rlib' -L 'native=C:\Program Files\MySQL\MySQL Connector C 6.1\lib\vs14'
Finished dev [unoptimized + debuginfo] target(scargo) in 37.52s
Running target\debug\diesel_api.exe

参考资料

https://stackoverflow.com/questions/57756927/rust-modules-confusion-when-there-is-main-rs-and-lib-rs

https://doc.rust-lang.org/edition-guide/rust-2018/module-system/path-clarity.html
2018和2015区别

https://doc.rust-lang.org/cargo/guide/project-layout.html
package应有的结构

https://doc.rust-lang.org/edition-guide/rust-2018/macros/macro-changes.html
2018和2015对宏引用的区别

你可能感兴趣的:(rust)