rust 快速入门
1 入门例子
hello.rs
// fn : function
fn main() {
// rust的缩进是4个空格
// println! 是一个 rust macro 宏, 不是函数, 如果结尾没有! 则是函数
println!("hello"); // 结尾 要有 ;
/* let 定义变量及初始化 */
// : i32 这种表示 变量x的类型
let x: i32 =6;
println!("{:?}", x);
let y: i32;
println!("{:?}", y); // 报错, 没有初始化会报错
// 定义了变量,但是没有使用它,rust会警告你,_开头的 rust不会警告
let _z = 5;
let a: i32;
// println!("{a}"); 使用之前必须初始化,否则报错
// 但是如果不使用的话, 是不会报错的
// _ 表示忽略这个变量绑定
let _ = 4;
// println!("{}", _); 报错, _ 不是变量名
}rustc 这个只适合编译简单的程序,大型程序 用cargo
将类型放到变量的后面的设计, 是考虑了rust想要类型自动推导.
2 immutable
3 shadowing
fn main() {
let a: i32 = 5;
// a=2; 会直接报错,提示不能修改 不可变的变量
println!("{:p}", &a);
// shadowing 隐藏了上面的变量a
let a: i32 = 5;
// 与上面的地址不同, 已经是一个不同的变量了,只是用了相同的变量名
// 一开始申明的a 被隐藏了
println!("重新申明的a变量地址: {:p}", &a);
let b = 5i32;
{
// 在这个作用域中, b 也确实shadowing 了外面的b
let b = 8i32;
println!("b 在作用域中的值: {b}"); // 是8
}
// 但是在退出块作用域后, b还是5
println!("作用域外,b的值:{b}");
// shadowing 可以使用完全不同的类型, 因为实际就是2个完全不同的变量
let b = "abc";
println!("{b}");
}一个变量转换类型后,想要用原来的变量名, 就用shadowing方式使用同名变量即可
希望变量一开始可读写,之后想要弄成只读
4 原生类型
primitive type
4.1 标量类型
scalar type
标量类型是指那些不可再分的、基本的、单个值的数据类型。 标量类型通常用于存储单一的数据项,如一个数字、一个字符或一个布尔值。 与标量类型相对的是复合数据类型,如数组、列表、对象和结构体,这些类型可以包含多个值
4.1.1 整型
use std::mem;
fn main() {
// 有符号 i8 i16 i32 i64 i128 isize(根据系统架构决定)
// 无符号 u8 u16 u32 u64 u128 usize(根据系统架构决定)
let a=6; // 不写类型,默认就是i32
// 打印i32类型的大小
println!("{}",mem::size_of::<i32>());
// 打印变量占用的内存大小
println!("{}",mem::size_of_val(&a));
let mut inferred_type = 12; // 根据下一行的赋值推断为 i64 类型
inferred_type = 4294967296i64;
// 十进制 2_100 下划线只是为了增加可读性, 这里相当于美国的千位加,的意思
let b:i32=2_100;
println!("{}",b);
// hex 16进制 0x 开头
let c:i32=0xff;
println!("{}",c);
// 八进制 0o 开头
let d:i32=0o12;
println!("{}",d);
// 二进制
let e:i32=0b1111_0000;
println!("{}",e);
// byte
let f:u8=b'A';
println!("{}",f);
// 可以使用类型后缀, 在值后边写上
let g=5i16;
println!("{}",g);
}fn main() {
println!("{}", i32::MAX); // i32最大值
println!("{}", 2_u16.pow(4)); // 求指数幂
println!("{}", (-4_i32).abs()); // 求绝对值
println!("{}", 0b0011_1101_u8.count_ones()); // 位计数(多少个1)
// 可以使用这种方式来调用方法
println!("{}", i32::abs(-4));
println!("{}", i8::count_zeros(0b0011_1101));
// Checked_ 操作返回一个结果的 Option 值:如果运算结果可以被结果类型正确表示就返
// 回 Some(v),否则返回 None
assert_eq!((10_u8).checked_add(20), Some(30));
assert_eq!((100_u8).checked_add(200), None);
// wrapping_ 的结果是对 2的8次方 256的取模得到的余数
assert_eq!(16_u8.wrapping_mul(15), 240);
assert_eq!(18_u8.wrapping_mul(15), 14);
// 有符号数的操作可能会回环成负数。
// 0111 1111 + 1 =>1000 0000 补码的 -128
assert_eq!(127_i8.wrapping_add(1), -128);
// 0111 1111 + 1+1 => 补码 1000 0001 => 反码:1000 0000 ==> 1111 1111 原码
assert_eq!(127_i8.wrapping_add(2), -127);
// 在移位操作中,移动的位数会回环到该类型的位数之内
// 因此对 8位的数字移动 9位等于移动 1位
// 0000 0101 相当于左移 1位=> 0000 1010
assert_eq!(5_i8.wrapping_shl(9), 10);
// Saturating_ 操作会返回最接近正确结果的表示,结果被“截断”到这个类型能表示的最大或最小值
assert_eq!(127_i8.saturating_add(1), 127);
// Overflowing_ 操作返回一个 tuple (result, overflowed),其中result 是回环版本的方法
// 返回的结果,而 overflowed 是一个指示是否发生溢出的 bool 值:
assert_eq!(255_u8.overflowing_sub(2), (253, false));
assert_eq!(255_u8.overflowing_add(2), (1, true));
// 只有当位移距离大于等 于类型的位宽度时 overflowed 才为 true
// 实际的移位距离等于要求的距离对位宽度取余后的结果
// 0000 0101 相当于左移1位 (9%8)=> 0000 1010
assert_eq!(5_u8.overflowing_shl(9), (10, true));
// 0000 0101 左移6位=> 1 0100 0000: 64
assert_eq!(5_u8.overflowing_shl(6), (64, false));
// is_numeric 数字,字符0-9 , 还有这以外,也有是数字的
println!("{}", '①'.is_numeric()); //true
}4.1.2 浮点数
fn main() {
// 平方根
println!("{}", 8f32.sqrt());
println!("{}", 8.2f32.floor()); // 8
println!("{}", 8.9f32.floor()); // 8
println!("{}", (-8.9f32).floor()); // -9
// 方法调用的优先级高于前缀运算符,因此对负数调用方法时确保要用括号括起来
println!("{}", -8.9f32.floor()); // -8
println!("{}", 8.2f32.round()); // 8
println!("{}", 8.5f32.round()); // 9
}4.1.3 bool
4.1.4 char
char 设计的目的是用来存储任何一个unicode 字符, 所以它的大小是4个字节
use std::mem;
fn main() {
let c = 'z'; // 这个是字符, 和前面 b'z' 是整型不同哦
println!("{}", mem::size_of_val(&c)); // 4
let z: char = 'ℤ';
println!("{c}-{z}");
// rust 中的char 是 4个字节,Unicode, 可以表示表情
let heart_eyed_cat = '😻';
println!("{heart_eyed_cat}");
let a = 'a';
println!("{}", a as u8); // 97
// 标准库提供了函数 std::char::from_u32 接受
// 任何 u32 值,并返回 Option<char>:
// 如果 u32 的值不是合法的 Unicode 码点,from_u32 会返回 None;
// 否则,它会返回 Some(c),c 就是作为转换结果的 char
let hex_char = char::from_u32(97);
println!("{:?}", hex_char);
let tr = '\n'; // 换行
let good = '\u{597D}'; // 好 unicode
println!("world {} {}", tr, good);
let a = 'c';
println!("{}", a.is_alphabetic()); // 是否为字母
let b = 'ß'; // 这个是非 ASCII 字符, 很多也被认为是字母
println!("{}", b.is_alphabetic()); // true
}4.2 复合类型
compound type
4.2.1 tuple
4.2.2 数组
- 元素类型必须一致
- 长度是固定的,运行时不能修改, 长度是类型的一部分
- 表示方法: [T;n] T表示泛型,后面会说, 这里就理解为数组元素的类型, n 表示元素个数
- 数组是在栈上分配的单个块的内存
- 常用于开辟一个固定大小的Buffer作为缓冲区,比如接收IO输入输出等
- 等学了所有权之后,我们知道把数组作为参数传递给函数,是直接复制一份给函数参数,而不是我们某些其他语言里传递指针
fn main() {
let x = [1, 2, 3, 4]; // 默认类型是[i32; 4]
println!("{}", x[1]);
// [i32;5] 表示每个元素是i32,一共有5个元素
let x: [i32; 5] = [1, 2, 3, 4, 5];
println!("{}", x.len());
// 相当于 let x=[3,3,3,3,3];
let x = [3; 5];
// 下标读取
println!("{}", x[2]);
// 空数组
let x: [i8; 0] = [];
println!("{}", std::mem::size_of_val(&x)); // 0
// is_empty() 源码就是 判断 self.len() == 0
println!("{}", x.is_empty()); // true
// 数组长度是类型的一部分, 只有元素类型和元素个数一样的才是同一种数组类型
let mut a=[1,2,3,4];
let b=[2,3,4,5];
a=b; // ok
unsafe {
// 可以去看unsafe 章节
// 数组的裸指针,类型是数组元素类型 这里是i32
let ptr = a.as_ptr();
println!("{:p}-{:p}", a.as_ptr(), &a);
println!("{}-{}", *ptr, *ptr.wrapping_add(1))
}
}- 数组不鼓励用索引进行操作, 因为我们知道每次索引操作都会进行边界检查,看不是是溢出了,效率略有影响
- 推荐后面说到的迭代器
4.2.3 切片
- 医学上切片的意思是将组织样本切成薄片以便于在显微镜下观察,可以说切片是原物品的一部分
- 编程上切片的意思: 原数据一段连续部分的引用,不难推断出它的数据结构是一个原数据的地址和引用的长度
use std::mem;
fn main() {
let mut x = [1, 2, 3];
println!("{}", mem::size_of_val(&x)); // 12
println!("{:p}", x.as_ptr());
// 切片是对一块连续内存数据的引用
// 由2个部分组成, 一个是data指向数据,一个len 表示长度
// 这种比一般指针要多出一些信息的叫 胖指针
let y = &mut x[1..3];
println!("{:p}", y.as_ptr());
println!("{}", y.len());
println!("{}", mem::size_of_val(&y)); // 16
let x = [1, 2, 3, 4, 5];
let (a, b) = x.split_at(2);
println!("{:?}", a); // [1,2]
println!("{:?}", b); // [3,4,5]
}// &[T] 切片的表示方式
fn analyze_slice(slice: &[i32]) {
println!("First element of the slice: {}", slice[0]);
println!("The slice has {} elements", slice.len());
}
fn main() {
let x: [i32; 5] = [2, 1, 4, 3, 5];
let y = &x[1..3];
analyze_slice(&y);
let mut chaos = [3, 5, 4, 1, 2];
//这里会隐式的把数组的引用转换为切片
chaos.sort();
println!("{:?}", chaos);
chaos.reverse();
println!("{:?}", chaos);
let x: [i32; 5] = [2, 1, 4, 3, 5];
// 也是隐式地转换为切片类型,才会可以for 循环
for i in &x {
println!("x {}", i);
}
}5 类型转换
- Rust 不提供原生类型 之间的隐式类型转换(coercion),但可以使用 as 关键字进行显式类型转换(casting)
- rust 是静态强类型, 需要显示表明你要转换的类型
5.1 as
5.2 transmute
不安全的转换, 需要 unsafe
use std::mem;
use std::any::type_name;
fn print_type_of<T>(_: &T) {
println!("{}", type_name::<T>());
}
fn main() {
// 这个元素的类型会根据你后面的transmute 里的src-type 来推断
// 之前在数组那里我们了解到 let x = [1, 2, 3, 4]; => 默认类型是[i32; 4]
let raw_bytes = [1, 2, 3, 4];
print_type_of(&raw_bytes);
// transmute::<src-type, dst-type>(src:src-type)
let num = unsafe { std::mem::transmute::<[u8; 4], u32>(raw_bytes) };
// 100 00000011 00000010 00000001
println!("{:b}", num);
println!("{}", num); // 67305985
let num = 67305985u32;
let r = unsafe { std::mem::transmute::<u32, [u8; 4]>(num) };
println!("{:?}", r); // [1, 2, 3, 4]
}指针类型转换
fn main() {
let a = 67109377;
// 将变量a的地址 转换为一个指针 ,指针类型是i32,这个就是a原来的内存读取方式
// 然后再将这个指针 进行类型转换, 转换为 数组的方式来读取 a 所在的那块内存
let r1 = &a as *const i32 as *mut [i8; 4];
unsafe {
println!("{:?}", (*r1));
(*r1)[0] = 5;
println!("{:?}", (*r1));
println!("{}", a);
}
}下面这个操作, 没有得到你想要的结果, 元组可能因为内存对齐的原因, a的前2个字节组成元组的第二个元素, 看看就好.
5.3 使用trait
直接看 类型转换相关trait
6 类型别名
// 定义一个类型别名
// 这使得代码更加简洁,更具可读性, 因为 Coordinates 比 (f64, f64) 更能表达其用途
type Coordinates = (f64, f64);
// 使用类型别名
fn calculate_distance(point1: Coordinates, point2: Coordinates) -> f64 {
let (x1, y1) = point1;
let (x2, y2) = point2;
((x2 - x1).powi(2) + (y2 - y1).powi(2)).sqrt()
}
fn main() {
let point_a: Coordinates = (0.0, 0.0);
let point_b: Coordinates = (3.0, 4.0);
let distance = calculate_distance(point_a, point_b);
println!("距离: {}", distance);
}fn main() {
// 类型别名
// 让类型含义更加的有意义
type Kilometers = i32;
let x: i32 = 5;
let y: Kilometers = 5;
// 实际还是是i32 所以可以 +
println!("x + y = {}", x + y);
}下面几个例子,有些知识点在后续介绍
减少重复代码, 让代码更简洁
type Thunk = Box<dyn Fn() + Send + 'static>;
let f: Thunk = Box::new(|| println!("hi"));
fn takes_long_type(f: Thunk) {
// --snip--
}
fn returns_long_type() -> Thunk {
// --snip--
}我们可以看到 Result<usize, Error> 类似这样的很多, 都要写Error
use std::fmt;
use std::io::Error;
pub trait Write {
fn write(&mut self, buf: &[u8]) -> Result<usize, Error>;
fn flush(&mut self) -> Result<(), Error>;
fn write_all(&mut self, buf: &[u8]) -> Result<(), Error>;
fn write_fmt(&mut self, fmt: fmt::Arguments) -> Result<(), Error>;
}
fn main() {}使用别名后的情况
use std::fmt;
// std::io 中有这样一个别名
// 标准库中类似这样的别名设计有还多
// type Result<T> = std::result::Result<T, std::io::Error>;
// 我们本地这样设计一个别名, 就不用每次 都写上Error
type Result<T> = std::io::Result<T>;
pub trait Write {
fn write(&mut self, buf: &[u8]) -> Result<usize>;
fn flush(&mut self) -> Result<()>;
fn write_all(&mut self, buf: &[u8]) -> Result<()>;
fn write_fmt(&mut self, fmt: fmt::Arguments) -> Result<()>;
}
fn main() {}7 静态变量
- 使用
static,一般在所有其他作用域之外声明, 作为全局变量
不可变静态变量
struct Animal {
age: i32,
}
impl Drop for Animal {
fn drop(&mut self) {
println!("animal 离开作用域了...{}", self.age);
}
}
fn main() {
let a = 22;
{
// 申明时必须初始化,必须指定类型
static b: Animal = Animal { age: 11 };
} // 不会调用 drop , 虽然无法访问b, 但是它不会被释放内存
let c = 11;
println!("{}", a);
{
let d = Animal { age: 100 };
} // 这个会调用drop
println!("{}", c);
}可变的静态变量,修改需要unsafe
static mut COUNTER: u32 = 22;
const COUNTER2: i32 = 11;
static mut S1: &str = "hello";
const S2: &str = "world";
fn main() {
println!("{:p}", &COUNTER2);
unsafe {
println!("{:p}", &COUNTER);
}
unsafe {
let s11 = S1.as_ptr(); // *const u8
println!("S1 指向的字符所在内存地址: {:p}", s11);
println!("{}", *s11); // 第一个h 字符
S1 = "world";
let s11 = S1.as_ptr();
// let s11 = S1.as_ptr() as *mut u8;
println!("S1 新指向的字符所在内存地址: {:p}", s11);
println!("{}", *s11);
let s22 = S2.as_ptr();
println!("S2 指向的字符所在内存地址:{:p}", s22);
}
}如果需要第一次使用才去初始化的静态变量 ,可以使用库once_cell,lazy_static
8 常量
- 常量是在编译期就进行求值的. 所以常量 = 的右边是可以使用表达式的
- 常量在编译过程中会被内联优化,这个表示每个使用常量的地方都会直接替换为常量的值(如果没记错的话, 可类比汇编中的立即数..),不会占用内存
- 因此修改常量的值本身就没有意思
9 函数
- 语句: 执行动作, 没有返回值
- 表达式: 是有一个值的
- 函数体是由一系列语句和一个结尾(可以是语句或者表达式)组成
- unit type 单元类型 () 表示一种类型. 表示没有什么特殊的价值, 它的值就是它本身 也是()
9.1 返回值
fn five() -> i32 {
// 表达式 ,值为5
5
}
fn main() {
let x = five();
println!("The value of x is: {x}");
}添加;号试试, 会直接报错提示 期望i32, 而实际发现是the unit type ()
我们将上面的返回值类型改成 () , 这样就不会报错了
实际上就是不用写返回值 的类型
9.2 函数作为参数
9.3 函数作为返回值
9.4 函数内部创建函数
9.5 函数的类型
fn add1(t: (i32, i32)) -> i32 {
t.0 + t.1
}
// 实际看来 与add1 参数一样
fn add2((t1, t2): (i32, i32)) -> i32 {
t1 + t2
}
fn main() {
let a = 1;
let b = 2;
let c = (1, 2);
println!("{}", add1(c));
println!("{}", add2((a, b)));
// f1的类型是 fn (t: (i32, i32)) -> i32
let f1 = add1;
// 报错了 expected fn item, found a different fn item
// 不同的类型, 所以报错了.
// f1 = add2;
// 正确的方式是 定义变量时将 他的类型转换为 fn item 通用的fn 类型
let f1 = add1 as fn((i32, i32)) -> i32;
println!("{}", f1((1, 2)));
// 这样定义也可以
let mut f1: fn((i32, i32)) -> i32 = add1;
f1 = add2; // 不会报错了
println!("{}", f1((a, b)));
}9.6 发散函数
diverging functions
返回值是 ! ,被称为 The Never Type
我们知道前面提到的函数其实都是有返回值, 即使是没有返回值的函数, 也会返回一个 () 单元类型的值:()
那么一个 永远不会返回的函数它有返回值吗? 还有 panic 这种函数呢?
fn main() {
let a = bar();
a=2;// 虽然不会执行到这里, 但是编译器不会报错
println!("{}", a);
}
// 返回 ! never type
fn bar() -> ! {
// 永远不会返回,但是有rust 设计它有返回值, 类型是!
loop {
print!("and ever ");
}
}
fn foo() ->!{
panic!("error");
}9.6.1 为什么需要发散类型
下面我们都知道会报错
fn main() {
let guess = "42";
let guess = match guess.trim().parse() {
Ok(_) => 5,
Err(_) => "hello", // 报错了
};
}下面这个没有报错
10 控制流程
10.1 if
fn main() {
let number = 3;
// 报错的, 需要bool类型
if number {
println!("number was three");
}
let condition = true;
// 当成 三元 运算, 实际上这里的表达更加清晰,一看就明白
// 而三元运算你是需要稍微学习或者说记一下
// 这也是有些语言它不去支持三元运算的原因
let number = if condition { 5 } else { 6 };
println!("The value of number is: {number}");
let number2 = if condition {
5;
7 // 还是看的返回值
} else {
6
};
println!("The value of number is: {number2}");
// 报错
let number3 = if condition { 5 } else { "six" };
}10.2 loop
fn main() {
let mut i = 0;
let ret = loop {
i += 1;
if i > 10 {
break i; // 返回值, 就是loop的返回值
}
};
println!("{}", ret)
let y: i32;
// 编译器是能够判断出 后面的打印时, y肯定已经赋值了.
let x = loop {
y = 1;
break;
};
println!("{:?}", x);
println!("{:?}", y);
}跳出多层 loop
#![allow(unreachable_code, unused_labels)]
fn main() {
'outer: loop {
println!("Entered the outer loop");
'inner: loop {
println!("Entered the inner loop");
// This would break only the inner loop
//break;
// This breaks the outer loop
break 'outer;
}
println!("This point will never be reached");
}
println!("Exited the outer loop");
}10.3 for
- for 循环是利用迭代器对数据进行遍历的,参考后面迭代器章节
- 标签 label的使用方式与loop一样
fn main() {
// label 前面有个 ' 单引号
'outer: for i in 0..5 {
println!("Outer loop value: {}", i);
for j in 0..5 {
println!("Inner loop value: {}", j);
if i == 2 && j == 3 {
break 'outer;
}
}
}
let a = [10, 20, 30, 40, 50];
for element in a {
println!("the value is: {element}");
}
// (1..4) 1 2 3
// 会提示不需要 用 () 将1..4 阔气来
for number in (1..4) {
println!("{number}!");
}
// 不包含4
for number in 1..4 {
println!("{number}!");
}
// =4 包含4
for number in 1..=4 {
println!("{number}");
}
for number in 1.. {
if number == 10 {
break;
}
println!("{}", number);
}
use std::ops::Range;
let a = Range { start: 1, end: 10 };
for j in a {
println!("{}", j);
}
// x 实际上是一个RangeFrom<i32> 类型
// 0..5 这种实际就是一个语法糖
let x = (0..);
// x 实际为Range类型
let x = (0..5);
}数组来测试 range
#[cfg(test)] //配置测试模块
mod abc {
#[test] //单元测试用例
fn it_works() {
let result = 2 + 2;
assert_eq!(result, 4);
}
#[test]
fn arr_range() {
let arr = [0, 1, 2, 3, 4];
assert_eq!(arr[..], [0, 1, 2, 3, 4]);
assert_eq!(arr[..3], [0, 1, 2]);
assert_eq!(arr[..=3], [0, 1, 2, 3]);
assert_eq!(arr[1..], [1, 2, 3, 4]);
assert_eq!(arr[1..3], [1, 2]); // This is a `Range`
assert_eq!(arr[1..=3], [1, 2, 3]);
assert_eq!((3..5), std::ops::Range { start: 3, end: 5 });
}
}10.4 while
11 测试
12 查看汇编
13 打印信息
13.1 打印变量大小
13.2 打印变量类型名
13.3 常用打印
- format!: 格式化文本到 String
- print!: 同format! 只不过输出到 (io::stdout).
- println!: 同print! 但是换行
- eprint!: 同 print! 只不过输出到 (io::stderr).
- eprintln!: 同eprint! 但是换行
fn main() {
println!("{} days", 31);
// 多个可以使用 0 1 指定
println!("{0}, this is {1}. {1}, this is {0}", "Alice", "Bob");
let x = "Alice";
let y = "Bob";
println!("{x}, this is {y}. {y}, this is {x}");
println!("{a}, this is {b}. {b}, this is {a}", a = "Alice", b = "Bob");
println!("Base 10: {}", 69420); // 69420
println!("Base 2 (binary): {:b}", 69420); // 10000111100101100
println!("Base 8 (octal): {:o}", 69420); //八进制: 207454
println!("Base 16 (hexadecimal): 0x{:x}", 69420); //16进制: 10f2c
println!("Base 16 (hexadecimal): 0x{:X}", 69420); //大写16进制: 10F2C
println!("科学计数法: {:e}", 10000i32); // 1e4
println!("科学计数法 大写: {:E}", 10000i32); // 1E4
println!("带换行和缩进的debug打印: {:#?}", [1, 2, 3]);
println!("debug打印: {:?}", [1, 2, 3]);
// > 表示往右对齐,那么就是左边填充
println!("|{number:>5}|", number = 1); // 没写表示用空格填充, 4个空格 和一个1
println!("{number:0>5}", number = 1); // 用0填充 : 00001
println!("{number:0<5}", number = 1); // 用0填充 : 10000
// 注意 需要 $
println!("{number:0>width$}", number = 1, width = 5);
let number: f64 = 1.0;
let width: usize = 5;
println!("{number:>width$}");
// 浮点数 四舍五入
println!("Dog: {:.3} ", 1.12385); // 1.124
}13.4 Debug
- 所有实现了
fmt::Debug这个trait(暂时不用管这个概念)的类型,才能使用println!("{:?}")中{:?}或{:#?}这种打印方式 - 可自动推导,使用
#[derive(Debug)]
13.5 Display
- 所有实现了
fmt::Display这个trait(暂时不用管这个概念)的类型,才能使用println!("{}")中{}这种打印方式 - 必须手动实现
use std::fmt;
struct Point2D {
x: f64,
y: f64,
}
// implement `Display` for `Point2D`.
impl fmt::Display for Point2D {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
// 这里的写法类似 println! ,根据需要自定义输出信息
write!(f, "x: {}, y: {}", self.x, self.y)
}
}
fn main() {
let p = Point2D { x: 3.3, y: 7.2 };
println!("p: {}", p);
// 实现了Display, 就能.to_string() 获取字符串 String
println!("{}", p.to_string());
}14 main()
14.1 返回值
rust main 入口函数返回值必须实现了Termination trait, trait后面章节讲, 先理解为接口.
// 返回值是 (), () 是实现了Termination trait
fn main(){
// 编译运行后, echo $? 结果是0
}
fn main() -> ! {
// 编译运行后, echo $? 结果是1
std::process::exit(1);
}
fn main() -> impl std::process::Termination {
std::process::ExitCode::SUCCESS
}
// 或者返回类型是 Result<T, E> where T: Termination, E: Debug 其他实现了Termination的 : Infallible.
14.2 命令行参数
fn main() {
// ./main ac -ver 1.2
let args_struct = std::env::args();
println!("{:?}", args_struct);
let args: Vec<_> = args_struct.collect();
println!("程序名: {}", &args[0]);
println!("命令行参数: {:?}", &args[1..]); // ["ac", "-ver", "1.2"]
let first_arg = match args.get(1) {
Some(arg) => arg,
None => {
println!("没有提供足够的参数");
return;
}
};
let number = match first_arg.parse::<i32>() {
Ok(num) => num,
Err(_) => {
println!("无法将第一个参数解析为整数");
return;
}
};
println!("第一个参数作为整数是: {}", number);
}14.3 环境变量
15 属性
15.1 dead_code
- 编译器 默认会对 dead_code(死代码, 无效代码) 进行检查, 这会对未使用的函数产生警告. 可以用一个属性来禁用这个检查项