第N次入门Rust - 6.Rust项目的代码组织


前言

这一篇主要介绍Rust项目应该如何组织代码~

在我看来代码组织对实际使用代码进行开发来说是非常重要的,但初学Rust的时候被模块系统的内容特别是Package和Crate的关系搞蒙了。后面结合像java的maven对项目的组织方式,从Cargo对项目的组织有一点新的理解。

  • Rust代码组织主要包括:
    • 哪些细节可以暴露,哪些细节是私有的。
    • 作用域内哪些名称有效。
    • 使用cargo怎么根据配置信息控制编译器工作。
  • 代码组织的内容统称为模块系统,模块系统包括下列概念:
    • Package(包):Cargo的特性,让开发者构建、测试、共享crate。
    • Crate(单元包):一个模块树,它可以产生一个library或者binary。
    • Module(模块)mod关键字和use关键字:控制代码组织,作用域、私有路径。
    • Path(路径):为Struct、Function或Module等项命名的方式。
  • 这篇文章还会介绍如何使用cargo去管理工作空间,毕竟这是一个很必要的功能。

6.1 Package(包)和Crate(单元包)

6.1.1 基础概念

  • Package(包)是提供一系列功能的一个或者多个Crate。
    • 包含一个Cargo.toml文件,并在其中描述如何构建这些crate。
  • Crate的类型:
    • binary(二进制项),即平时我们双击执行的可执行文件。
    • library(库),供其它Crate使用。
  • package和crate的关系,一个package包括:
    • 只能包含0-1个library crate。
    • 可以包含任意数量的binary crate。
    • 必须至少包含一个crate,无论是library crate或者binary crate。
  • crate root(crate根):是一个源文件,是rust编译器的起点,并构成开发者的crate的根module。Cargo把crate root文件交给rustc来构建library或binary。
  • 注意Package和Crate的区别:
    • 它俩都是包,但是Crate把包按照使用方式划分为二进制项和库;
    • 当讲到依赖第三方库的时候往往都是说依赖了第三方crate,而不是第三方package,但由于依赖的都是library crate,而一个package只能有一个library crate,所以对于一个Package以外来说,依赖包和依赖crate说法上我觉得是没有冲突的。

6.1.2 使用cargo命令创建Package

  • cargo new package-name:创建package,主要内容包含配置文件Cargo.toml,以及一个binary crate的入口文件src/main.rs
  • cargo new --lib package-name:创建package,主要内容包含配置文件Cargo.toml,以及一个library crate的入口文件src/lib.rs

6.1.3 Package结构

Package和Crate的关系

|-package-name/
	|-Cargo.toml
	|-src/
		|-main.rs
		|-lib.rs
		|-bin/
			|-bin1.rs
			|-bin2.rs
  • 配置文件Cargo.toml:使用cargo进行package内容管理的配置文件。
  • src/main.rs:binary crate的crate root,从main.rs作为入口编译的crate的名字与package相同,编译出来的可执行文件名字与crate名字相同。
  • src/lib.rs:library crate的crate root,从lib.rs作为入口的crate的名字与package相同。
  • src/bin/xxx.rs:binary crate的,从xxx.rs作为入口编译的crate的名字,编译出来的可执行文件名为xxx,其中xxx是自定义的名字,src/bin/下面可以有多个crate入口文件。

6.1.4 Cargo.toml

  • Cargo.toml里面可以进行非常多的配置(比如开发配置和生产配置,可执行文件名称等),无需把所有内容都了解了才能使用,遇到什么不会的再查文档就可以了。
  • 使用cargo创建package后Cargo.toml的默认配置:
    [package]				# package的配置
    name = "package-name"	# package名字
    version = "0.1.0"		# 版本号
    edition = "2021"		# rust批次号
    
    [dependencies]			# 依赖第三方crate管理
    rand = "0.5.5"
    

6.1.5 编译Package和Crate

  • 如果既有src/main.rssrc/lib.rssrc/xxx.rs等crate root,在package根目录(Cargo.toml所在目录)执行cargo命令:
    • 把所有crate都编译出来:cargo build
    • 只编译src/lib.rs作为入口的libary crate,执行carge build --lib(因为一个package里面只能有一个libary crate,所以无需指定名字);
    • 只编译src/main.rs作为入口的binary crate,执行cargo build --bin package-name
    • 只编译src/bin/xxx.rs作为入口的binary crate,执行cargo build --bin xxx
    • 运行某个binary crate,执行cargo run --bin package-namecargo run --bin xxx

6.2 Module(模块)

  • 定义module来控制作用域和私有性。

  • module(模块):

    • 在一个crate内,将代码进行分组。
    • module可以增加可读性,并使代码易于复用。
    • 可以用于控制项(item)的私有性,是public的还是private的。
  • 建立module:

    • 需要有mod关键字
    • module的建立可以嵌套
    • 可以在module中包含其它项(struct, enum, 常量, trait, 函数等)的定义
    • 定义模块:
      mod 模块名 {
          // 模块内容
      }
      
  • 例子:

    mod front_of_house {
        mod hosting {
            fn add_to_waitlist() {}
            fn seat_at_table() {}
        }
    
        mod serving {
            fn take_order() {}
            fn serve_order() {}
            fn task_payment() {}
        }
    }
    
  • Module Tree(模块树):

    • src/main.rssrc/lib.rs为Crate Root,这两个文件(任意一个)的内容形成名为crate的模块,位于整个模块树的根部。
    • 其他的模块都会直接或间接地接到crate模块上,即crate模块为所有模块的祖先模块。
  • Module Tree例子:

    crate
     └── front_of_house             // 模块
         ├── hosting                // 子模块
         │   ├── add_to_waitlist    // 子模块中的内容
         │   └── seat_at_table
         └── serving                // 子模块
             ├── take_order         // 子模块中的内容
             ├── serve_order
             └── take_payment
    

6.3 Path(路径)

为了在Rust的模块中找到某个项(条目),需要使用路径。

6.3.1 绝对路径和相对路径

  • 路径的两种形式:

    • 绝对路径(absolute path):从crate root开始,以crate名或者字面值crate开头。
    • 相对路径(relative path):从当前模块开始,以self(当前模块)、super(上一级模块)或当前模块的标识符开头。
  • 路径至少由一个标识符组成,标识符之间使用::进行连接。

  • 要使用绝对路径还是相对路径,取决于项目需要。

    • 如果调用方和被调用方经常一起移动,则倾向使用相对路径访问。
    • 如果调用方和被调用方可能会分开移动,则倾向使用绝对路径。
    • 通常更倾向使用绝对路径,因为把代码定义和项调用各自独立地移动是更常见的。
  • 绝对路径与相对路径的例子:

    mod front_of_house {
        pub mod hosting {
            pub fn add_to_waitlist() {}
            fn seat_at_table() {}
        }
    
        mod serving {
            fn take_order() {}
            fn serve_order() {}
            fn task_payment() {}
        }
    }
    
    
    pub fn eat_at_restaurant() {
        // 绝对路径访问
        crate::front_of_house::hosting::add_to_waitlist();
        // 相对路径访问
        front_of_house::hosting::add_to_waitlist();
    }
    
    fn main() {
    }
    

6.3.2 super关键字在路径中的使用

  • super关键字:用来访问父级模块路径中的内容,即在路径中super代表的是当前模块的上一级模块。
  • super的使用例子:
    mod restaurant {
        fn serve_order() {}
    
        mod back_of_houst {
            fn cook_order() {}
            fn action() {
                super::serve_order();
                cook_order();
            }
        }
    }
    

6.4 私有性边界(privacy boundary)

6.4.1 私有性

  • 模块不仅可以组织代码,还可以定义私有边界。

  • 如果想把函数、struct等项设置为私有,可以将它们放入到某个模块中。

  • rust中所有项(函数、方法、struct、enum、模块和常量)默认都是私有的。

    • 如果要将某些项标记为公共的,需要使用pub关键字标记它。
  • 父级模块无法访问子模块中的私有项(条目):因为私有性就是为了隐藏实现细节。

  • 子模块里可以使用所有祖先模块中的公有和私有项(条目)。

  • 个人理解:注意这里所说的A使用XX项,并不是说能使用XX及其内部的项,更准确地说,应该是能知道XX的存在,但是并不知道XX内部有什么存在,如果想要察觉到XX内部的YY的存在,就必须满足两个条件:1.XX对A是可见的(可能XX与A是同一级的兄弟项,也可能是XX向外暴露了自己的存在),2.YY是在XX中是向外暴露的。

  • 如何向外暴露一个项的存在:使用pub关键字修饰这个项。一个项公有的,但是其子项默认还是私有的

  • 也就是说一个项被知晓的程度既取决于自身是否是公有项,也取决于其父级项是否是公有项。

  • 一个项知晓哪些项的存在:

    • 自身:self
    • 父项:super
    • 根:crate
    • 兄弟项
    • 上述项中对外暴露的项以及递归暴露项
  • 语法:

    mod front_of_house {
        pub mod hosting {
            pub fn add_to_waitlist() {}
        }
    }
    
    pub fn eat_at_restaurant() {
        // Absolute path
        crate::front_of_house::hosting::add_to_waitlist();
    
        // Relative path
        front_of_house::hosting::add_to_waitlist();
    }
    
    • front_of_houseeat_at_restaurant的兄弟项,所以front_of_house没有暴露eat_at_restaurant也能发现它。
  • super的使用主要是为了避免当一个项中有子项和兄弟项同名时无法定位的问题。

6.4.2 pub struct

  • 要让struct变成公共的,只需在struct前面加一个pub关键字。
  • struct中的字段默认是私有的,除非使用pub修饰字段使其变成公有的。
  • pub struct例子:
    mod back_of_house {
        // Beakfast在back_of_house同级中可见
        pub struct Breakfast {
            pub toast: String,      // toast字段外部可见
            seasonal_fruit: String, // seasonal_fruit字段外部不可见
        }
    
        impl Breakfast {
            pub fn summer(toast: &str) -> Breakfast {
                Breakfast {
                    toast: String::from(toast),
                    seasonal_fruit: String::from("peaches"),
                }
            }
        }
    }
    
    pub fn eat_at_restaurant() {
        let mut meal = back_of_house::Breakfast::summer("Rye");
        meal.toast = String::from("Wheat");
        println!("I'd like {} toast please", meal.toast);
        
        // 因为seasonal_fruit字段不是公共的,所以在外部无法访问。
        // meal.seasonal_fruit = String::from("blueberries");           
    }
    

6.4.3 pub enum

  • 要让enum变成公共的,只需在enum前面加一个pub关键字。
  • 如果一个enum变为公共的,那么它的变体也会全变成公共的(注意,此时变体前面不需要加上pub关键字),因为如果enum的变体不能被访问就失去了它们的意义了。
  • pub enum:
    mod back_of_house {
        // Apperizer被标记为pub,即使它的变体没有被标记为pub,也全是外部可见的
        pub enum Appetizer {
            Soup,
            Salad,
        }
    }
    
    pub fn eat_at_restaurant() {
        let salad = back_of_house::Appetizer::Salad;
    }
    

6.5 use关键字 和 as关键字

6.5.1 基本用法

  • 可以使用use关键字将路径导入到作用域内。
    • 导入的路径仍然遵守私有性原则,即只有导入路径中的公共部分才能被访问。
  • 使用use引入路径时可以引入绝对路径,也可以引入相对路径。
  • use的习惯用法:
    • 函数:将函数的父级模块引入作用域(指定到父级)。
    • struct、enum等:指定完整路径(指定到本身)。
    • 同名条目:指定到父级。
  • as关键字可以为引入的路径指定本地的别名。
  • use支持的语法:
    use crate::xxx:yyy;         // 使用yyy,yyy可以是各种项
    use crate::xxx:*;           // 引入xxx下所有内容
    use crate::{xxx:yyy, zzz};  // 嵌套引入:一行里面引入多个同一棵模块子树下面的项
    use crate::xxx:zzz as z3;   // 起别名
    pub use crate::xxx:yyy;     // 重导出(re-exporting)
    
  • 例子:
    mod front_of_house {
        pub mod hosting {
            pub fn add_to_waitlist() {}
            pub fn get_waitlist() {}
            fn some_function() {}
        }
    }
    
    mod restaurant {
        
        // use crate::front_of_house::hosting;
        use front_of_house::hosting;
        // crate::front_of_house::hosting::add_to_waitlist();
        
        use front_of_house::hosing::get_waitlist as gw
    
        pub fn eat_at_restaurant() {
            hosting::add_to_waitlist();
            // hosting::some_function();    // 访问不到
            
            gw();
        }
    }
    

6.5.2 pub use

  • 使用use将路径导入到作用域内后,该名称在此作用域内是私有的,即当前作用域外是看不到这个use导入的路径。
  • 如果要使use导入的路径在当前作用域外也能看到,可以使用pub use导入路径。
  • pub use:重导出
    • 将条目引入作用域
    • 该条目可以被外部代码引入到它们的作用域

6.6 使用外部包(Package)

  1. Cargo.toml添加依赖的包(package)。具体做法是在Cargo.tomldependencies下加入依赖名 = "语义版本描述",比如:rand = "0.5.5"。cargo会从https://crates.io/中获取这个包和这个包的依赖项并下载到本地。
  2. 使用use将包中的项引入到需要用到的作用域中,use 依赖名::xxx
  • std是属于标准库,也会被当成外部包,但是它已经默认被引入了,不需要在Cargo.toml中配置就可以直接使用。但如果要用里面的一些项,还是要引入,如:use std::collections::HashMap;
  • 有的时候下载依赖中断了,再次下载依赖的时候会提示Blocking waiting,这个时候可以将$CARGO_HOME中的.package-cache删掉,然后从新拉取。

6.7 模块系统管理

  • 随着模块的逐渐变大,单一的文件无法将整个crate中整个模块的内容包包含其中,此时可以将每个模块的代码单独保存在文件/目录中,再通过在上级模块中声明下级模块,从而实现将模块内容分成不同文件的操作。

6.7.1 模块拆分规则

  • 如果foo模块没有子模块,将foo模块的代码放在foo.rs文件中。
  • 如果foo模块由子模块,有两种处理方式:
    • foo模块的代码放在foo.rs文件中,并将其子模块所在文件存放在foo/目录。
    • foo模式的代码放在foo/mod.rs中,并将其子模块所在文件存放在foo/目录。(类似于python的模块结构)

例子:

|-phrases_lib/
    |-Cargo.toml				# package配置文件
    |-src/
        |-lib.rs				# library crate入口
        |						# 使用第一种方式管理模块
        |-chinese.rs            # > 该文件存放chinese模块的内容,并包含使用mod关键字挂载它的子模块的语句
        |-chinese/				# > 该目录下存放chinese模块下属的子模块的内容
            |-farewells.rs
            |-greetings.rs
        |						# 使用第二种方式管理模块
        |-english/              # > 该目录下存放english模块自身内容及子模块的内容
            |-mod.rs			# > 该目录下存放english模块自身的内容,并包含使用mod关键字挂载它的子模块的语句
            |-farewells.rs		# > 该文件存放english模块的子模块的内容
            |-greetings.rs		# > 该文件存放english模块的子模块的内容
  • 注意mod关键字和use关键字的功能的区别:
    • mod用于声明模块之间的结构关系,在一个模块的源文件中使用mod起到的作用是声明挂载子模块,当然如果子模块比较简单的时候也可以在模块中声明的同时定义子模块内部逻辑。
    • use用于声明要引入其它模块的项的信息,引入一个项,从而可以在当前上下文中直接使用这个项。当然很多时候引入的项会是模块、struct、enum等。它实际上是简化路径的一种技术。

6.7.2 例子:模块拆分前

  • 可以认为,src/main.rssrc/lib.rs是模块crate的作用域,假设在不进行内容拆分之前,所有代码都放在src/main.rs中,则目录结构和代码分别如下。

目录结构:

|-src/
    |-main.rs

代码:

// src/main.rs
mod front_of_house {
    pub mod hosting {
        pub fn add_to_waitlist() { println!("add to wait list"); }

        pub mod inner_hosting {
            pub fn inner_serve() {
                println("inner_serve");
            }
        }
    }
}

mod back_of_house {
}

pub fn eat_at_restaurant() {
    use front_of_house::{hosting, hosting::inner_hosting};
    hosting::add_to_waitlist();
    inner_hosting::inner_serve();
}

从上面代码可知模块树结构如下:

|-crate
    |-front_of_house[mod]
        |-hosting[mod]
            |-inner_hosting[mod]
            |-add_to_waitlist[fn]
    |-back_of_house[mod]
    |-eat_at_restaurant[fn]

6.7.3 例子:模块拆分后

代码目录结构:

|-src
    |-main.rs
    |-front_of_house.rs
    |-front_of_house/
        |-hosting.rs
        |-hosting/
            |-inner_hosting.rs
    |-back_of_house.rs

src/main.rs

mod front_of_house;
mod back_of_house;

pub fn eat_at_restaurant() {
    use front_of_house::{hosting, hosting::inner_hosting};
    hosting::add_to_waitlist();
    inner_hosting::inner_serve();
}

src/front_of_house.rs

pub mod hosting;

src/front_of_house/hosting.rs

pub mod inner_hosting;

pub fn add_to_waitlist() { println!("add to wait list"); }

src/front_of_house/hosting/inner_hosting.rs

pub fn inner_serve() {
    println!("inner serve");
}

src/back_of_house.rs

// 空

6.8 Cargo 工作空间

  • 前面有讲到,一个Package可以管理多个Crate,但是想要只使用一个Package管理一个大项目还是有所不妥。

  • Cargo工作空间(workspaces)的功能类似于Maven的Module,即将一个庞大的项目拆分成多个功能相对独立的Package,比如说将项目拆分为三Package:product-management,order-management,user-management

  • 把整个大项目的每一个Package都放到一个工作空间中进行管理,使用工作空间后项目的结构大致如下:

    my-web-shop/
        ├── Cargo.toml				# 工作空间的配置文件
        ├── Cargo.lock
        ├── product-management/		# 第一个Package
        │   ├── Cargo.toml
        │   └── src/
        │   	├── main.rs
        │       └── lib.rs
        ├── order-management/		# 第一个Package
        │   ├── Cargo.toml
        │   └── src
        │   	├── main.rs
        │       └── lib.rs
        ├── user-management			# 第二个Package
        │   ├── Cargo.toml
        │   └── src
        │   	├── main.rs
        │       └── lib.rs
        └── target/					# 编译输出
    
  • my-web-shop的Cargo.toml(包含工作空间的配置信息)如下:

    [workspace]
    members = [
        "product-management",
        "order-management",
        "user-management"
    ]
    
    • 目前使用cargo new指令无法之间创建工作空间,因此还是建议先新建一个目录,然后手动创建一个Cargo.toml文件,然后手动加上[workspace]以指定该目录是一个工作空间;
    • 当处于工作空间所在根目录下执行cargo new创建Package时,cargo并不会直接在Cargo.tomlworkspace.members中增加新Package的信息(即不会把新创建的包纳入工作空间管理中),终端只会返回信息当前目录是一个工作目录,此时需要手动设置一下workspace.members
  • 工作空间在顶级目录有一个 target 目录,每一个Package内没有target目录,即使进入子Package中运行cargo build,生成的结果依然会保存在工作空间/target中,这样让所有的Package共享一个target目录,可以避免其他Package多余的重复构建。

  • 子Package之间互相依赖:默认情况下cargo不假定工作空间中的crate会互相依赖,所欲要显式声明crate之间的依赖关系。具体做法为,比如order-management依赖同级的user-management,则在order-management/Cargo.toml中需要声明:

    [dependencies]
    
    user-management = { path = "../user-management" }
    
  • 如果要在工作空间中运行指定的二进制crate,需要增加-p参数和包名称来指定:

    cargo run -p product-management
    
  • 由此可见Cargo.toml可以作为Package的配置,也可以作为工作空间的配置。

  • 工作空间中使用外部依赖:

    • 由于整个工作空间及子目录中只有根目录的一个Cargo.lock,因此工作空间根上的src/*.rs使用的依赖与每一个子Package的依赖的所有版本信息都交由工作空间根目录的Cargo.lock约束。
    • 一个子Package A依赖了某个外部库a,如果子Package B或者工作空间的根没有在对应的Cargo.toml中声明使用a,那么只有Package A能使用外部库a,但是约束信息还是会保存在根Cargo.lock
    • 如果其它Package或者根也使用外部库a,则由于Cargo.lock的存在,会强制要求工作空间中任何地方都只能用相同版本的外部库a。
  • 工作空间中测试:

    • 运行所有测试:cargo test
    • 指定某个子crate测试:cargo test -p mp-core
  • 工作空间中发布:如果需要单独发布每一个子Package,需要进入到对应的目录中执行cargo publish

6.9 设置生成配置和开发配置

  • Cargo 有两个主要的配置:
    • 运行 cargo build 时采用的 dev 配置。dev 配置被定义为开发时的好的默认配置。
    • 运行 cargo build --releaserelease 配置。release 配置则有着良好的发布构建的默认配置。
  • 项目的 Cargo.toml 文件中没有任何 [profile.*] 部分的时候,Cargo 会对每一个配置都采用默认设置。通过增加任何希望定制的配置对应的 [profile.*] 部分,可以选择覆盖任意默认设置的子集。
    • 下面的例子,为设置编译优化,默认情况下dev的优化等级为0,release的优化等级为3。更多的参数设置可以查看Cargo的文档
    [profile.dev]
    opt-level = 0
    
    [profile.release]
    opt-level = 3
    

6.10 将crate发布到crate.io

6.10.1 编写文档注释

  • 文档注释(documentation comments):使用三斜杠///开头的Markdown文本,要求文档注释位于文档的项之前(即是什么函数或者结构体的文档就写在这个函数或者结构体之前)。
  • 生成文档:运行cargo doc,可以自动根据文档注释生成对应的HTML文档,并放在target/doc目录中。
  • 构建文档并浏览:cargo doc --open
  • 常用的文档注释内容:
    • # Examples:例子章节,通常会在这个章节中用三反引号括着调用例子,除了起到说明的作用,还能作为文档测试样例。
    • # Panics:异常章节,说明会抛出panic!的场景。
    • # Errors:错误章节,如果函数返回Result,此部分会描述代码会出现什么错误并且出现这种错误的原因,方便调用者编写代码处理错误。
    • # Safety:如果函数使用了unsafe代码,这一部分应该会涉及到期望函数调用者支持的确保 unsafe 块中代码正常工作的不变条件(invariants)。

6.10.2 文档注释作为测试

  • 在文档注释中增加代码块(用markdown的三反引号包裹着),可以在cargo test的时候被作为测试样例运行。
  • 虽然这种测试方法非常棒,既起到说明的作用,也起到测试的作用,但是在写完文档以后如果改动了代码,需要同步更改文档中的测试代码。

6.10.3 注释包含项的结构

  • 另一种风格的文档注释,以双斜杠加感叹号开头(//!):专门用于crate根文件(通常是src/lib.rs)或模块的根文件为crate或模块整体提供文档。用于做一个整体说明。

下面是某个src/lib.rs的开头

//! # My Crate
//!
//! `my_crate` is a collection of utilities to make performing certain
//! calculations more convenient.

/// Adds one to the number given.
// --snip--

6.10.4 创建crates.io账号

  • 发布crate之前,需要在crates.io上注册账号并获得一个API token(登录后在 https://crates.io/me/ 获取)。
  • 在本地使用API token登录cargo login 你的token,这个命令会通知 Cargo 你的 API token 并将其储存在本地的 ~/.cargo/credentials 文件中。
  • 这个token相当于私钥,可以在crates.io上重新生成。

6.10.5 完善crate的信息

  • Cargo.toml[package]部分增加一些本crate的元信息,常用元信息的例子如下:
    [package]
    name = "guessing_game"
    version = "0.1.0"
    authors = ["Your Name <you@example.com>"]
    edition = "2018"
    description = "A fun game where you guess what number the computer has chosen."
    license = "MIT OR Apache-2.0"
    
    [dependencies]
    ...
    

6.10.6 发布crate和撤回版本

  • 发布crate:cargo publish
    • 发布是永久性的,已存在的版本不会被覆盖,代码也不会被删除,因此要小心。
    • 如果要发布一个新版本的crate,只需要修改Cargo.toml中的version字段,根据语义化版本规则规定下一个版本号。
  • 虽然不能删除已发布版本的crate,但是可以阻止其他开发者在后续新项目依赖某个版本的crate,已使用该版本依赖的项目仍然可以使用这个被撤回的依赖。
    • 本质上撤回意味着所有带有 Cargo.lock 的项目的依赖不会被破坏,同时任何新生成的 Cargo.lock 将不能使用被撤回的版本。
    • 撤回一个指定版本的 crate:
      cargo yank --vers 1.0.1
      
    • 撤销撤回操作,并允许项目可以再次开始依赖某个版本,通过在命令上增加 --undo
      cargo yank --vers 1.0.1 --undo
      

6.11 cargo install

  • cargo install 命令用于在本地安装和使用二进制 crate。它并不打算替换系统中的包;它意在作为一个方便 Rust 开发者们安装其他人已经在 crates.io 上共享的工具的手段。只有拥有二进制目标文件的包能够被安装。二进制目标 文件是在 crate 有 src/main.rs 或者其他指定为二进制文件时所创建的可执行程序,这不同于自身不能执行但适合包含在其他程序中的库目标文件。通常 crate 的 README 文件中有该 crate 是库、二进制目标还是两者都是的信息。
  • 所有来自 cargo install 的二进制文件都安装到 Rust 安装根目录的 bin 文件夹中。如果使用 rustup.rs 安装的 Rust 且没有自定义任何配置,这将是 $HOME/.cargo/bin。确保将这个目录添加到 $PATH 环境变量中就能够运行通过 cargo install 安装的程序了。

6.12 Cargo 自定义扩展命令

  • Cargo 的设计使得开发者可以通过新的子命令来对 Cargo 进行扩展,而无需修改 Cargo 本身。
  • 如果 $PATH 中有类似 cargo-something 的二进制文件,就可以通过 cargo something 来像 Cargo 子命令一样运行它。像这样的自定义命令也可以运行 cargo --list 来展示出来。
  • 0
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
要配置rust-analyzer的代码提示,你可以按照以下步骤进行操作: 1. 确保已经在你的项目中安装了rust-analyzer插件。你可以在VSCode的插件市场中搜索并安装"rust-analyzer"插件。 2. 打开VSCode的设置(可以通过菜单栏的"文件" -> "首选项" -> "设置"或者使用快捷键Ctrl + ,打开设置)。 3. 在设置页面的搜索框中输入"rust-analyzer",找到相关的设置选项。 4. 根据你的需求,配置下列常用的代码提示相关的设置: - "rust-analyzer.enable": 设置为true以启用rust-analyzer插件。 - "rust-analyzer.completion.enable": 设置为true以启用代码补全功能。 - "rust-analyzer.completion.addCallArgumentSnippets": 设置为true以自动添加函数调用时的参数提示。 - "rust-analyzer.completion.addCallParenthesis": 设置为true以自动添加函数调用时的括号。 - "rust-analyzer.completion.postfix.enable": 设置为true以启用后缀代码补全功能,例如`.if`、`.let`等。 - "rust-analyzer.hover.enable": 设置为true以启用悬停提示功能。 - "rust-analyzer.inlayHints.enable": 设置为true以启用内联提示功能。 5. 根据你的需求,可以进一步自定义配置rust-analyzer的代码提示行为。你可以在设置中找到更多相关的选项,并根据注释进行配置。 6. 保存设置,并重启VSCode使更改生效。 通过以上步骤,你可以根据自己的喜好和需求来配置rust-analyzer的代码提示功能。请注意,具体的配置选项可能会因rust-analyzer插件版本的不同而有所差异,请参考插件的官方文档或参考其它资源获取更多定制化的配置信息。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值