【Rust】包和模块,文档注释,Rust格式化输出

news2024/11/17 16:51:08

文章目录

  • 包和模块
    • 包 Crate
      • Rust 的标准目录结构
    • 模块 Module
      • 用路径引用模块
      • 使用super引用模块
      • 使用self引用模块
      • 结构体和枚举的可见性
    • 使用 use 引入模块及受限可见性
      • 基本引入方式
        • 绝对路径引入模块
        • 相对路径引入模块中的函数
      • 避免同名引用
  • 注释和文档
    • 文档注释
    • 包和模块级别的注释
    • 注释的一个例子
  • 格式化输出
    • 对齐
    • 精度
    • 进制

包和模块

Rust 提供了相应概念用于代码的组织管理:

  • 项目(Packages):一个 Cargo 提供的 feature,可以用来构建、测试和分享包
  • 包(Crate):一个由多个模块组成的树形结构,可以作为三方库进行分发,也可以生成可执行文件进行运行
  • 模块(Module):可以一个文件多个模块,也可以一个文件一个模块,模块可以被认为是真实项目中的代码组织单元
  • 工作空间(WorkSpace):对于大型项目,可以进一步将多个包联合在一起,组织成工作空间

包 Crate

对于 Rust 而言,包是一个独立的可编译单元,它编译后会生成一个可执行文件或者一个库。

由于 Package 就是一个项目,因此它包含有独立的 Cargo.toml 文件,以及因为功能性被组织在一起的一个或多个包。一个 Package 只能包含一个库(library)类型的包,但是可以包含多个二进制可执行类型的包。

创建一个二进制 Package

$ cargo new my-project
     Created binary (application) `my-project` package
$ ls my-project
Cargo.toml
src
$ ls my-project/src
main.rs

这里,Cargo 创建了一个名称是 my-projectPackage,同时在其中创建了 Cargo.toml 文件,可以看一下该文件,里面并没有提到 src/main.rs 作为程序的入口,原因是 Cargo 有一个惯例:src/main.rs 是二进制包的根文件,该二进制包的包名跟所属 Package 相同,在这里都是 my-project,所有的代码执行都从该文件中的 fn main() 函数开始。

使用 cargo run 可以运行该项目,输出:Hello, world!

​ 再来创建一个库类型的 Package

$ cargo new my-lib --lib
     Created library `my-lib` package
$ ls my-lib
Cargo.toml
src
$ ls my-lib/src
lib.rs

首先,如果你试图运行 my-lib,会报错:

$ cargo run
error: a bin target must be available for `cargo run`

原因是库类型的 Package 只能作为三方库被其它项目引用,而不能独立运行,只有之前的二进制 Package 才可以运行。

Rust 的标准目录结构

一个真实项目中典型的 Package,会包含多个二进制包,这些包文件被放在 src/bin 目录下,每一个文件都是独立的二进制包,同时也会包含一个库包,该包只能存在一个 src/lib.rs

.
├── Cargo.toml
├── Cargo.lock
├── src
│   ├── main.rs
│   ├── lib.rs
│   └── bin
│       └── main1.rs
│       └── main2.rs
├── tests
│   └── some_integration_tests.rs
├── benches
│   └── simple_bench.rs
└── examples
    └── simple_example.rs
  • 唯一库包:src/lib.rs
  • 默认二进制包:src/main.rs,编译后生成的可执行文件与 Package 同名
  • 其余二进制包:src/bin/main1.rssrc/bin/main2.rs,它们会分别生成一个文件同名的二进制可执行文件
  • 集成测试文件:tests 目录下
  • 基准性能测试 benchmark 文件:benches 目录下
  • 项目示例:examples 目录下

模块 Module

模块是Rust的代码构成单元。使用模块可以将包中的代码按照功能性进行重组,最终实现更好的可读性及易用性。同时,我们还能非常灵活地去控制代码的可见性,进一步强化 Rust 的安全性。

  • 使用 mod 关键字来创建新模块,后面紧跟着模块名称
  • 模块可以嵌套,这里嵌套的原因是招待客人和服务都发生在前厅,因此我们的代码模拟了真实场景
  • 模块中可以定义各种 Rust 类型,例如函数、结构体、枚举、特征等
  • 所有模块均定义在同一个文件中

用路径引用模块

两种引用模块的路径:

  • 绝对路径,从包根开始,路径名以包名或者 crate 作为开头
  • 相对路径,从当前模块开始,以 selfsuper 或当前模块的标识符作为开头

Rust 出于安全的考虑,默认情况下,所有的类型都是私有化的,包括函数、方法、结构体、枚举、常量,就连模块本身也是私有化的。父模块完全无法访问子模块中的私有项,但是子模块却可以访问父模块、父父…模块的私有项

Rust 提供了 pub 关键字,通过它可以控制模块和模块中指定项的可见性。

使用super引用模块

super 代表的是父模块为开始的引用方式,非常类似于文件系统中的 .. 语法

fn serve_order() {}

mod back_of_house {
    fn fix_incorrect_order() {
        cook_order();
        super::serve_order();
    }

    fn cook_order() {}
}

使用self引用模块

self 其实就是引用自身模块中的项,也就是说和我们之前章节的代码类似,都调用同一模块中的内容

结构体和枚举的可见性

结构体和枚举成员字段拥有完全不同的可见性:

  • 将结构体设置为 pub,但它的所有字段依然是私有的
  • 将枚举设置为 pub,它的所有字段也将对外可见

使用 use 引入模块及受限可见性

在 Rust 中,可以使用 use 关键字把路径提前引入到当前作用域中,随后的调用就可以省略该路径,极大地简化了代码。

基本引入方式

在 Rust 中,引入模块中的项有两种方式:绝对路径和相对路径

绝对路径引入模块
mod front_of_house {
    pub mod hosting {
        pub fn add_to_waitlist() {}
    }
}

use crate::front_of_house::hosting;

pub fn eat_at_restaurant() {
    hosting::add_to_waitlist();
    hosting::add_to_waitlist();
}
相对路径引入模块中的函数

在下面代码中,我们不仅要使用相对路径进行引入,而且与上面引入 hosting 模块不同,直接引入该模块中的 add_to_waitlist 函数:

mod front_of_house {
    pub mod hosting {
        pub fn add_to_waitlist() {}
    }
}

use front_of_house::hosting::add_to_waitlist;

pub fn eat_at_restaurant() {
    add_to_waitlist();
    add_to_waitlist();
}

从使用简洁性来说,引入函数自然是更甚一筹,但是在某些时候,引入模块会更好:

  • 需要引入同一个模块的多个函数
  • 作用域中存在同名函数

在以上两种情况中,使用 use front_of_house::hosting 引入模块要比 use front_of_house::hosting::add_to_waitlist; 引入函数更好。

例如,如果想使用 HashMap,那么直接引入该结构体是比引入模块更好的选择,因为在 collections 模块中,我们只需要使用一个 HashMap 结构体:

use std::collections::HashMap;

fn main() {
    let mut map = HashMap::new();
    map.insert(1, 2);
}

其实严格来说,对于引用方式并没有需要遵守的惯例,不过建议优先使用最细粒度(引入函数、结构体等)的引用方式,如果引起了某种麻烦(例如前面两种情况),再使用引入模块的方式

避免同名引用

避免同名冲突的关键,就是使用父模块的方式来调用,除此之外,还可以通过as给予引入的项起一个别名,它可以赋予引入项一个全新的名称:

use std::fmt::Result;
use std::io::Result as IoResult;

fn function1() -> Result {
    // --snip--
}

fn function2() -> IoResult<()> {
    // --snip--
}

当外部的模块项 A 被引入到当前模块中时,它的可见性自动被设置为私有的,如果你希望允许其它外部代码引用我们的模块项 A,那么可以对它使用pub use进行再导出:use 代表引入模块到当前作用域,pub 表示将该引入的内容再度设置为可见。

注释和文档

在 Rust 中,注释分为三类:

  • 代码注释,用于说明某一块代码的功能,读者往往是同一个项目的协作开发者
  • 文档注释,支持 Markdown,对项目描述、公共 API 等用户关心的功能进行介绍,同时还能提供示例代码,目标读者往往是想要了解你项目的人
  • 包和模块注释,严格来说这也是文档注释中的一种,它主要用于说明当前包和模块的功能,方便用户迅速了解一个项目

代码注释:行注释//,块注释/* ...... */

文档注释

Rust 提供了 cargo doc 的命令,可以用于把这些文档注释转换成 HTML 网页文件,最终展示给用户浏览,这样用户就知道这个包是做什么的以及该如何使用。

文档行注释///和文档块注释/** ...... */

注意:

  • 文档注释需要位于 lib 类型的包中,例如 src/lib.rs
  • 文档注释可以使用 markdown语法!例如 # Examples 的标题,以及代码块高亮
  • 被注释的对象需要使用 pub 对外可见,记住:文档注释是给用户看的,内部实现细节不应该被暴露出去

运行 cargo doc 可以直接生成 HTML 文件,放入target/doc目录下。使用 cargo doc --open 命令,可以在生成文档后,自动在浏览器中打开网页。

常用文档标题:

  • Panics:函数可能会出现的异常状况,这样调用函数的人就可以提前规避
  • Errors:描述可能出现的错误及什么情况会导致错误,有助于调用者针对不同的错误采取不同的处理方式
  • Safety:如果函数使用 unsafe 代码,那么调用者就需要注意一些使用条件,以确保 unsafe 代码块的正常工作

包和模块级别的注释

可以给包和模块添加注释,需要注意的是,这些注释要添加到包、模块的最上方。包级别的注释也分为两种:行注释 //! 和块注释 /*! ... */

注释的一个例子

这个例子我们将重点应用几个知识点:

  • 文档注释
  • 一个项目可以包含两个包:二进制可执行包和 lib 包(库包),它们的包根分别是 src/main.rssrc/lib.rs
  • 在二进制包中引用 lib
  • 使用 pub use 再导出 API,并观察文档

首先,使用 cargo new art 创建一个 Package art

Created binary (application) `art` package

系统提示我们创建了一个二进制 Package,该 Package 包含一个同名的二进制包:包名为 art,包根为 src/main.rs,该包可以编译成二进制然后运行。

现在,在 src 目录下创建一个 lib.rs 文件,创建该文件等于又创建了一个库类型的包,包名也是 art,包根为 src/lib.rs,该包是是库类型的,因此往往作为依赖库被引入。

将以下内容添加到 src/lib.rs 中:

//! # Art
//!
//!  未来的艺术建模库,现在的调色库

pub use self::kinds::PrimaryColor;
pub use self::kinds::SecondaryColor;
pub use self::utils::mix;

pub mod kinds {
    //! 定义颜色的类型

    /// 主色
    pub enum PrimaryColor {
        Red,
        Yellow,
        Blue,
    }

    /// 副色
    #[derive(Debug,PartialEq)]
    pub enum SecondaryColor {
        Orange,
        Green,
        Purple,
    }
}

pub mod utils {
    //! 实用工具,目前只实现了调色板
    use crate::kinds::*;

    /// 将两种主色调成副色
    /// ```rust
    /// use art::utils::mix;
    /// use art::kinds::{PrimaryColor,SecondaryColor};
    /// assert!(matches!(mix(PrimaryColor::Yellow, PrimaryColor::Blue), SecondaryColor::Green));
    /// ```
    pub fn mix(c1: PrimaryColor, c2: PrimaryColor) -> SecondaryColor {
        SecondaryColor::Green
    }
}

在库包的包根 src/lib.rs 下,我们又定义了几个子模块,同时将子模块中的三个项通过 pub use 进行了再导出。

接着,将下面内容添加到 src/main.rs 中:

use art::kinds::PrimaryColor;
use art::utils::mix;

fn main() {
    let blue = PrimaryColor::Blue;
    let yellow = PrimaryColor::Yellow;
    println!("{:?}",mix(blue, yellow));
}

在二进制可执行包的包根 src/main.rs 下,我们引入了库包 art 中的模块项,同时使用 main 函数作为程序的入口,该二进制包可以使用 cargo run 运行:

Green

至此,库包完美提供了用于调色的 API,二进制包引入这些 API 完美的实现了调色并打印输出。

最后,再来看看文档长啥样:
在这里插入图片描述

格式化输出

先来一段代码,看看格式化输出的初印象:

println!("Hello");                 // => "Hello"
println!("Hello, {}!", "world");   // => "Hello, world!"
println!("The number is {}", 1);   // => "The number is 1"
println!("{:?}", (3, 4));          // => "(3, 4)"
println!("{value}", value=4);      // => "4"
println!("{} {}", 1, 2);           // => "1 2"
println!("{:04}", 42);             // => "0042" with leading zeros

可以看到 println! 宏接受的是可变参数,第一个参数是一个字符串常量,它表示最终输出字符串的格式,包含其中形如 {} 的符号是占位符,会被 println! 后面的参数依次替换。

  • print! 将格式化文本输出到标准输出,不带换行符
  • println! 同上,但是在行的末尾添加换行符
  • format! 将格式化文本输出到 String 字符串

在实际项目中,最常用的是 println!format!,前者常用来调试输出,后者常用来生成格式化的字符串:

fn main() {
    let s = "hello";
    println!("{}, world", s);
    let s1 = format!("{}, world", s);
    print!("{}", s1);
    print!("{}\n", "!");
}

其中,s1 是通过 format! 生成的 String 字符串,最终输出如下:

hello, world
hello, world!

{} 类似,{:?} 也是占位符:

  • {} 适用于实现了 std::fmt::Display 特征的类型,用来以更优雅、更友好的方式格式化文本,例如展示给用户
  • {:?} 适用于实现了 std::fmt::Debug 特征的类型,用于调试场景

大多数 Rust 类型都实现了 Debug 特征或者支持派生该特征:

#[derive(Debug)]
struct Person {
    name: String,
    age: u8
}

fn main() {
    let i = 3.1415926;
    let s = String::from("hello");
    let v = vec![1, 2, 3];
    let p = Person{name: "sunface".to_string(), age: 18};
    println!("{:?}, {:?}, {:?}, {:?}", i, s, v, p);
}

对于数值、字符串、数组,可以直接使用 {:?} 进行输出,但是对于结构体,需要派生Debug特征后,才能进行输出,总之很简单。

常用格式:

对齐

fn main() {
    // 以下全部都会补齐5个字符的长度
    // 左对齐 => Hello x    !
    println!("Hello {:<5}!", "x");
    // 右对齐 => Hello     x!
    println!("Hello {:>5}!", "x");
    // 居中对齐 => Hello   x  !
    println!("Hello {:^5}!", "x");

    // 对齐并使用指定符号填充 => Hello x&&&&!
    // 指定符号填充的前提条件是必须有对齐字符
    println!("Hello {:&<5}!", "x");
}

精度

精度可以用于控制浮点数的精度或者字符串的长度

fn main() {
    let v = 3.1415926;
    // 保留小数点后两位 => 3.14
    println!("{:.2}", v);
    // 带符号保留小数点后两位 => +3.14
    println!("{:+.2}", v);
    // 不带小数 => 3
    println!("{:.0}", v);
    // 通过参数来设定精度 => 3.1416,相当于{:.4}
    println!("{:.1$}", v, 4);

    let s = "hi我是Sunface孙飞";
    // 保留字符串前三个字符 => hi我
    println!("{:.3}", s);
    // {:.*}接收两个参数,第一个是精度,第二个是被格式化的值 => Hello abc!
    println!("Hello {:.*}!", 3, "abcdefg");
}

进制

可以使用 # 号来控制数字的进制输出:

  • #b, 二进制
  • #o, 八进制
  • #x, 小写十六进制
  • #X, 大写十六进制
  • x, 不带前缀的小写十六进制
fn main() {
    // 二进制 => 0b11011!
    println!("{:#b}!", 27);
    // 八进制 => 0o33!
    println!("{:#o}!", 27);
    // 十进制 => 27!
    println!("{}!", 27);
    // 小写十六进制 => 0x1b!
    println!("{:#x}!", 27);
    // 大写十六进制 => 0x1B!
    println!("{:#X}!", 27);

    // 不带前缀的十六进制 => 1b!
    println!("{:x}!", 27);

    // 使用0填充二进制,宽度为10 => 0b00011011!
    println!("{:#010b}!", 27);
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1082464.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

conda: error: argument COMMAND: invalid choice: ‘activate‘

参考:https://github.com/conda/conda/issues/13022 输入后重启terminal即可

Spring Boot 开发环境热部署

Spring Boot 项目无法像前端项目那样&#xff0c;修改源代码后刷新网页就能即时看到效果&#xff0c;需要先暂停运行&#xff0c;再重新启动&#xff0c;最后刷新网页。 为了避免这一麻烦的操作&#xff0c;我们可以设置热部署&#xff0c;启动服务后不论怎么修改源码&#xf…

NodeJs中使用JSONP和Cors实现跨域

跨域是为了解决浏览器请求域名&#xff0c;协议&#xff0c;端口不同的接口&#xff0c;相同的接口是不需要实现跨域的。 1.使用JSONP格式实现跨域 实现步骤 动态创建一个script标签 src指向接口的地址 定义一个函数和后端调用的函数名一样 实现代码 -- 在nodejs中使用http内…

【Mysql】重新认识mysql(一)

参考Mysql是怎么运行的&#xff0c;并结合实际的工作经验对mysql的知识进行总结。 Mysql架构 从大体上来说&#xff0c;Mysql是C/S架构。以我们平时使用的QQ为例&#xff0c;它其实是由两部分组成的&#xff0c;一部分是客户端程序,&#xff0c;一部分是服务器程序。客户端可…

【深蓝学院】手写VIO第6章--视觉前端--作业(SVD分解部分复习)

0. 题目 T1. 奇异值分解需要补&#xff0c;看这篇博客&#xff0c;讲的很好。 总结一下&#xff0c;关于奇异值分解(Singular Value Decomposition&#xff0c;SVD )有以下内容摘抄自该博客&#xff0c;关于SDV分解的部分应该是摘自李航《统计学习方法里面的》&#xff1a; 1…

互联网性能和可用性优化CDN和DNS

当涉及到互联网性能和可用性优化时&#xff0c;DNS&#xff08;Domain Name System&#xff09;和CDN&#xff08;Content Delivery Network&#xff09;是两个至关重要的元素。它们各自发挥着关键作用&#xff0c;以确保用户能够快速、可靠地访问网站和应用程序。在本文中&…

项目管理之常见七大问题挑战

在当今复杂多变的市场环境下&#xff0c;企业为了生存和发展&#xff0c;必须不断应对和解决各种挑战。其中&#xff0c;项目管理作为企业运营及项目交付等的重要组成部分&#xff0c;也面临着七大问题挑战。这些挑战不仅影响着项目的成功实施&#xff0c;也对企业的发展产生着…

[Spring] SpringMVC 简介(一)

目录 一、SpringMVC 简介 1、什么是 MVC 2、什么是 SpringMVC 3、SpringMVC 实现原理 4、SpringMVC 的特点 二、简单案例 1、引入依赖 2、在 web.xml 中配置前端控制器 DispatcherServlet 3、创建 SpringMVC 的配置文件 4、创建请求控制器 5、测试页面 6、访问不到 …

Go 存储系列:B+树存储引擎 boltdb

boltdb 介绍 boltdb是一个纯go编写的支持事务的文件型单机kv数据库 支持事务&#xff1a; boltdb数据库支持两类事务&#xff1a;读写事务、只读事务。这一点就和其他kv数据库有很大区别文件型&#xff1a; boltdb所有的数据都是存储在磁盘上的&#xff0c;所以它属于文件型数…

信号与系统第一章

文章目录 1.1连续信号与离散信号1.1.2信号能量与功率能量讨论无穷区间内功率和能量&#xff1a;无限区间内的平均功率&#xff1a;利用上述定义区分三种重要信号 1.2自变量的变换1.2.1举例基本变换1.2.2周期信号1.2.3偶信号与奇信号 1.3指数信号与正弦信号1.3.1连续时间复指数信…

LeetCode(力扣)416. 分割等和子集Python

LeetCode416. 分割等和子集 题目链接代码 题目链接 https://leetcode.cn/problems/partition-equal-subset-sum/ 代码 class Solution:def canPartition(self, nums: List[int]) -> bool:sum 0dp [0]*10001for num in nums:sum numif sum % 2 1:return Falsetarget …

python之计算市场技术指标

1、MA MA指标是一种常用的技术指标&#xff0c;它是通过计算一定时间内的股价平均值来反映股价趋势的指标。通常&#xff0c;MA指标越平滑&#xff0c;就能更好地反映出股价的长期趋势。 MA指标的作用是帮助投资者识别股票价格的趋势。当股票价格的MA指标向上运动时&#xff…

分类网络的评价指标

之前一直是做目标检测的研究&#xff0c;在目标检测中主要有两个任务&#xff0c;一个是分类回归&#xff0c;一个是位置回归&#xff0c;所用的评价指标有&#xff1a;AP&#xff0c;mAP&#xff0c;Recall&#xff0c;Precision&#xff0c;F1值&#xff0c;前两个用的一般最…

论文学习记录--零样本学习(zero-shot learning)

Socher R, Ganjoo M, Manning C D, et al. Zero-shot learning through cross-modal transfer[J]. Advances in neural information processing systems, 2013, 26. 注&#xff1a;中文为机翻 zero-shot learning&#xff1a;通过学习类别之间的关系和属性&#xff0c;使得模型…

每日leetcode_LCP01猜数字

每日leetcode_LCP01猜数字 记录自己的成长&#xff0c;加油。 题目出处&#xff1a;LCP 01. 猜数字 - 力扣&#xff08;LeetCode&#xff09; 题目 解题 class Solution {public int game(int[] guess, int[] answer) {int count 0;for (int i 0 ; i< guess.length; i){…

修正两个shapefile之间的数字化错误

目录 一、数据输入 二、测量拓扑容差 三、启用编辑 四、使用拓扑编辑工具 五、建立图层拓扑关系 六、待修改的图层设置为唯一可选图层 七、编辑折点 八、拖动折点至land图层折点 一、数据输入 二、测量拓扑容差 拓扑容差约为4~5m 三、启用编辑 四、使用拓扑编辑工具 五…

系统移植--前言

移植 不同架构的处理器指令集不兼容&#xff0c;即便是相同的处理器架构&#xff0c;板卡不同驱动代码也不兼容 Linux是一个通用的内核并不是为某一个特定的处理器架构或板卡设计的&#xff0c;所以从官方获取Linux源码后我们要先经过相应的配置使其与我们当前的硬件平台相匹配…

Java数据结构第十七章、手撕位图

给40亿个不重复的无符号整数&#xff0c;没排过序。给一个无符号整数&#xff0c;如何快速判断一个数是否在这40亿个数中。【腾讯】 1. 遍历&#xff0c;时间复杂度O(N) 2. 排序(O(NlogN))&#xff0c;利用二分查找: logN 3. 位图解决 数据是否在给定的整形数据中&#xff0c;结…

重置Mac电脑的SMC怎么操作,重置SMC方法分享~

SMC 负责管理 Mac 上的电源。重置 SMC 可以解决一些与电源或散热管理相关的不常见问题。今天重置SMC教程给大家分享一下&#xff0c;需要的小伙伴看过来&#xff01; 如何判断您是不是需要重置 SMC 若出现以下症状&#xff0c;则表明可能需要重置 SMC&#xff1a; 电池无法充电…

Exim 中的漏洞!

我们将继续分享有关流行网络安全漏洞的最新信息。 Exim 中的 CVE-2023-42115漏洞&#xff01; CVE-2023-42115是开源 Exim 4 SMTP 邮件服务器项目中的一个远程代码执行漏洞。问题的严重性不容小觑。据我们的分析师称&#xff0c;目前网络中存在漏洞的 Exim 服务器超过 350 万台…