-
Notifications
You must be signed in to change notification settings - Fork 20
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Rust 入门相关 #612
Comments
语法基础数据类型有符号整形:i8,i16,i32,i64和isize(指针大小) 常用类型转化
*可以看到i32, u32, f64(i代表init,u代表dobble,f代表float) 到String类型是同一个函数,这样的话,如何来确定要转换至的具体类型?就是类型推导!当然,如果rust无法通过上下文推导出类型,就需要给出提示,例如x.parse::().unwrap()。但是通常情况下,都不需要提示即可自动推导出类型。
&str/String/collections::string::String
Vec/&[T]/Box<[T]>
箭头 ->
双冒号 ::
|
问题收集
#![allow(unused_variables)]
fn main() {
let some_u8_value = 0u8;
match some_u8_value {
1 => println!("one"),
3 => println!("three"),
5 => println!("five"),
7 => println!("seven"),
_ => (),
}
} 在搜索网页后,我发现了很多使用这种表示法的例子 后缀文字
所以0u8是0作为无符号8位整数. 这些被称为“后缀文字”,并在Rust By Example详细讨论. |
rust Option/Result相关总结enum Result<T, E> {
Ok(T),
Err(E),
}
原因https://blog.csdn.net/readlnh/article/details/86613747 空值(Null )是一个值,它代表没有值。在有空值的语言中,变量总是这两种状态之一:空值和非空值。 这句话有点绕。实际上用过c++的同学们应该知道c++内的NULL的值是0,例如int *ptr = NULL,这里ptr的值就是0,这里的0不是实际意义上的0,而是用来表示一个空值,ptr是一个空指针。但是我们知道计算机里没有空这种东西,所以c++取了0来表示逻辑意义上的空。之所以取0,是因为在大多数操作系统里,程序是不允许访问地址为0的内存的,因为该内存是操作西用保留的。然而,内存地址 0 有特别重要的意义,它表明该指针不指向一个可访问的内存位置。但按照惯例,如果指针包含空值(零值),则假定它不指向任何东西。当然现在c++有nullptr了,关于nullptr和NULL,0的关系我这里就不讲了…不是我的重点。 之前提到,rust里面是没有空值的,那么空值有什么问题呢?它最大的问题就在于如果像使用非空值一样使用一个空值,会导致错误。然而空和非空的属性无处不在,所以非常容易出现这种错误。但是空值的概念仍然是有意的: 空值是一个因为某种原因目前无效或缺失的值。 因此虽然rust没有空值,但是仍然存在一个可以表达存在或不存在的概念的枚举,Option。 enum Option<T> {
Some(T),
None,
} 我们注意到,这里使用了范型。也就是说Option枚举的Some成员可以是任意的数据类型。需要注意的是如果使用None而不是Some时,需要告诉rustOption的类型。因为编译器只通过None是无法推断出Some的类型的。即 let x: Option<i32> = None; 这里的None就是和空值表达类似的东西。那为什么要使用None而不是空值呢?这里有一个很重要的一点,Option和T不是同一个类型。不要小看着一个简单的区别,这意味着Option和T是不能直接进行运算的,即Option和i32是不能直接相加的。实际上,更进一步的,在对Option进行T的运算时,必须先将Option转化成T类型。如此一来就可以帮助我们避免以为值非空而实际为空的情况。例如下面这段代码: fn main() {
let a: i32 = 1;
let b: Option<i32> = Some(5);
let c = a + b;
println!("{}", c);
} 编译器会报如下错误
我们必须先将b从Option类型转化为i32才能进行i32类型的运算。如下 fn main() {
let a: i32 = 1;
let b: Option<i32> = Some(5);
let c = a + b.unwrap();
println!("{}", c);
} 另外,每当我们引入一个可能为空的值时,我们必须先把它放到Option里。当我们使用这个值时,我们必须先处理值为空的情况。也就是说,只要一个值不是Option类型的,我们就可以认定它的值不为空。 这个设计相当有意思,我又查了一些资料发现scala里就存在这个设计,rust应该就是借鉴的scala的做法,无怪乎有人说rust参考了c/c++和haskell/scala两类语言。如果有机会应该多见识一下其他的语言,开阔下思路(虽然工作估计还是c/c++,(-_-||) 解耦Some(T)中T的值方式:第一种: let sp = Some(111);
if let Some(b) = sp{//if let 是rust语法糖形式
println!("{}", b);
}else{
println!("其它");
} 第二种: let sp = Some(111);
let v = sp.unwrap();//111 第三种: let mut x = Some(2);
match x.as_mut() {
Some(v) => *v = 42,
None => {},
}
assert_eq!(x, Some(42)); |
赞 |
编译相关build.rs
lib.rs
Cargo.toml: [package]
name = "mything"
version = "0.0.1"
authors = ["me <me@gmail.com>"]
[lib]
name = "mylib"
path = "src/lib.rs"
[[bin]]
name = "mybin"
path = "src/bin.rs" src/lib.rs: pub fn test() {
println!("Test");
} src/bin.rs: extern crate mylib; // not needed since Rust edition 2018 use mylib::test;
pub fn main() {
test();
} |
编译脚本支持(Build script support) 有些包需要编译第三方的非Rust代码,比如说C库。其他包需要链接到C库,这些C库可能位于系统磁盘上,也有可能需要从源代码中构建。其他人仍然需要一些功能,比如在构建之前的代码生成(考虑解释器生成器)(没明白是什么意思)。 Cargo并不是为了取代这些针对这些任务进行了良好优化的其他工具,但是它确实与构建配置选项集成在一起了: 一、如何使用构建脚本 在Cargo.toml文件中 [package] ...build = "build.rs" 注意:如果你没有为构建命令指定值,但是你的包根目录下确实存在一个"build.rs"文件,Cargo将会为你编译并调用这个文件。 构建命令的一些用例如下: 构建一个绑定的C库 一、构建脚本的输入 当构建脚本运行时,构建脚本有大量的输入,所有的输入都以环境变量的形式传递。而除了环境变量之外,构建脚本的当前目录是构建脚本包的源目录。 二、构建脚本的输出 通过构建脚本输出到stdout的所有行,都会被写到文件中如:target/debug/build//stdout(具体位置可能取决于你的配置)。任何以 "cargo:" 开头的行都会被Cargo直接解释。这一行必须是这种格式: cargo:key=value 如下所示: 复制代码 specially recognized by Cargocargo:rustc-link-lib=static=foo arbitrary user-defined metadatacargo:root=/path/to/foo Cargo会识别一些特殊的关键字,会一定程度上影响crate的构建方式: rustc-link-lib=[KIND=]NAME 表示指定的值是一个库名,应该将其作为 -l 标识传递给编译器。可选的KIND可以是static,dylib(默认),或者framework(只适用于苹果系统),可以通过rustc --help查看更多细节。 |
CORE 核心库ptr 指针操作copy_nonoverlapping
cellCell
slicefrom_raw_partsfrom_raw_parts_mut
mem
size_of
llvm_asm
llvm-as 是LLVM汇编程序。它读取包含人类可读LLVM汇编语言的文件,将其转换为LLVM字节码,并将结果写入文件或标准输出。 |
fururesasync
|
生命周期总结
使用&i32 // 常规引用
&'a i32 // 含有生命周期注释的引用
&'a mut i32 // 可变型含有生命周期注释的引用 // 错误
fn longer(s1: &str, s2: &str) -> &str {
if s2.len() > s1.len() {
s2
} else {
s1
}
}
// 可以,使用泛型来声明生命周期的名称,随后函数返回值的生命周期将与两个参数的生命周期一致
fn longer<'a>(s1: &'a str, s2: &'a str) -> &'a str {
if s2.len() > s1.len() {
s2
} else {
s1
}
}
// 经典函数,使用了泛型、特性、生命周期机制
use std::fmt::Display;
fn longest_with_an_announcement<'a, T>(x: &'a str, y: &'a str, ann: T) -> &'a str
where T: Display
{
println!("Announcement! {}", ann);
if x.len() > y.len() {
x
} else {
y
}
} 在以下三种情况可以自动推导生命周期
问答为什么要有生命周期?
|
rustEmpowering everyone to build reliable and efficient software.
特点
与 webassembly 结合
方向
安装
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
brew install rustup
rustup update # 更新
rustup self uninstall
rustc --version
cargo --version
rustup component add rustfmt --toolchain stable-x86_64-apple-darwin
rustup doc # 本地阅读核心文档
rustc main.rs
./main 配置
## cargo换源
# $HOME/.cargo/config
[source.crates-io]
registry = "https://github.com/rust-lang/crates.io-index"
replace-with = 'ustc'
[source.ustc]
registry = "git://mirrors.ustc.edu.cn/crates.io-index"
[source.tuna]
registry = "https://mirrors.tuna.tsinghua.edu.cn/git/crates.io-index.git"
[source.sjtu]
registry = "https://mirrors.sjtug.sjtu.edu.cn/git/crates.io-index"
[source.rustcc]
registry = "git://crates.rustcc.cn/crates.io-index"
[source.aliyun]
registry = "https://code.aliyun.com/rustcc/crates.io-index"
## rustup换源
# 上海交通大学
export RUSTUP_DIST_SERVER=https://mirrors.sjtug.sjtu.edu.cn/rust-static
export RUSTUP_UPDATE_ROOT=https://mirrors.sjtug.sjtu.edu.cn/rust-static/rustup
# 清华大学
echo 'export RUSTUP_DIST_SERVER=https://mirrors.tuna.tsinghua.edu.cn/rustup' >> ~/.bash_profile
RUSTUP_DIST_SERVER=https://mirrors.tuna.tsinghua.edu.cn/rustup
# 中国科学技术大学
RUSTUP_UPDATE_ROOT=https://mirrors.ustc.edu.cn/rust-static/rustup
RUSTUP_DIST_SERVER=https://mirrors.ustc.edu.cn/rust-static
# 官方
RUSTUP_UPDATE_ROOT=https://static.rust-lang.org/rustup
RUSTUP_UPDATE_ROOT=https://static.rust-lang.org 环境
Cargo
# Cargo.toml
cargo --verison
cargo new project
cargo run|check|update
cargo build --realse
cargo install wasm-pack # Compile Rust to Wasm and generate JS interop code
cargo install cargo-make # Task runner
cargo install simple-http-server # Simple server to serve assets
rustup component add rls --toolchain stable-x86_64-apple-darwin Project Setup
cargo new --lib rustmart && cd rustmart 模块系统
第三方箱存储库 crates.io
Syntax
Const
Variables
类型系统
类型安全
类型推导
通过泛型支持参数多态
pub enum Cow<'a, B: ?Sized + 'a> where B: ToOwned,
{
// 借用的数据
Borrowed(&'a B),
// 拥有的数据
Owned(<B as ToOwned>::Owned),
} 通过 trait 支持特设多态
用 trait object 支持子类型多态Data Types
![[../_static/rust_data_type.jpg]] Boolean
Number
// Addition, Subtraction, and Multiplication
println!("1 + 2 = {} and 8 - 5 = {} and 15 * 3 = {}", 1u32 + 2, 8i32 - 5, 15 * 3);
// Integer and Floating point division
println!("9 / 2 = {} but 9.0 / 2.0 = {}", 9u32 / 2, 9.0 / 2.0); String
元组 tuple
结构 struct
// Classic struct with named fields
struct Student { name: String, level: u8, remote: bool }
// Tuple struct with data types only
struct Grades(char, char, char, char, f32);
// Unit struct
struct Unit;
// Instantiate classic struct, specify fields in random order, or in specified order
let user_1 = Student { name: String::from("Constance Sharma"), remote: true, level: 2 };
let user_2 = Student { name: String::from("Dyson Tan"), level: 5, remote: false };
// Instantiate tuple structs, pass values in same order as types defined
let mark_1 = Grades('A', 'A', 'B', 'A', 3.75);
let mark_2 = Grades('B', 'A', 'A', 'C', 3.25);
println!("{}, level {}. Remote: {}. Grades: {}, {}, {}, {}. Average: {}",
user_1.name, user_1.level, user_1.remote, mark_1.0, mark_1.1, mark_1.2, mark_1.3, mark_1.4);
println!("{}, level {}. Remote: {}. Grades: {}, {}, {}, {}. Average: {}",
user_2.name, user_2.level, user_2.remote, mark_2.0, mark_2.1, mark_2.2, mark_2.3, mark_2.4); 枚举 enum
enum WebEvent {
// An enum variant can be like a unit struct without fields or data types
WELoad,
// An enum variant can be like a tuple struct with data types but no named fields
WEKeys(String, char),
// An enum variant can be like a classic struct with named fields and their data types
WEClick { x: i64, y: i64 }
}
// Define a tuple struct
struct KeyPress(String, char);
// Define a classic struct
struct MouseClick { x: i64, y: i64 }
// Redefine the enum variants to use the data from the new structs
// Update the page Load variant to have the boolean type
enum WebEvent { WELoad(bool), WEClick(MouseClick), WEKeys(KeyPress) }
let we_load = WebEvent::WELoad(true);
// Instantiate a MouseClick struct and bind the coordinate values
let click = MouseClick { x: 100, y: 250 };
// Set the WEClick variant to use the data in the click struct
let we_click = WebEvent::WEClick(click);
// Instantiate a KeyPress tuple and bind the key values
let keys = KeyPress(String::from("Ctrl+"), 'N');
// Set the WEKeys variant to use the data in the keys tuple
let we_key = WebEvent::WEKeys(keys); Array
// Declare array, initialize all values, compiler infers length = 7
let days = ["Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"];
// Declare array, first value = "0", length = 5
let bytes = [0; 5];
let arr = [1, 2, 3];
assert_eq!(arr[..], [1, 2, 3]);
assert_eq!(arr[0..=1], [1, 2]); Vector
// Create empty vector, declare vector mutable so it can grow and shrink
let mut fruit = Vec::new();
// Push values onto end of vector, type changes from generic `T` to String
fruit.push("Apple");
fruit.push("Banana");
fruit.push("Cherry");
println!("Fruits: {:?}", fruit);
println!("Pop off: {:?}", fruit.pop());
println!("Fruits: {:?}", fruit); 泛型
// Declare vector, initialize with three values
let three_nums = vec![15, 3, 46];
println!("Initial vector: {:?}", three_nums);
// Declare vector, first value = "0", length = 5
let zeroes = vec![0; 5];
println!("Zeroes: {:?}", zeroes); 哈希映射
use std::collections::HashMap;
let mut reviews: HashMap<String, String> = HashMap::new();
reviews.insert("Ancient Roman History".to_string(), "Very accurate.".to_string());
reviews.insert("Cooking with Rhubarb".to_string(), "Sweet recipes.".to_string());
reviews.insert("Programming in Rust".to_string(), "Great examples.".to_string());
// Look for a specific review
let book: &str = "Programming in Rust";
println!("\nReview for \'{}\': {:?}", book, reviews.get(book));
reviews.insert("Ancient Roman History".to_string(), "Very accurate.".to_string()); Object
智能指针
control loop 控制流程
let mut counter = 1;
// stop_loop is set when loop stops
let stop_loop = loop {
counter *= 2;
if counter > 100 {
// Stop loop, return counter value
break counter;
}
};
// Loop should break when counter = 128
println!("Break the loop at counter = {}.", stop_loop);
fn process_event(event: &Event) {
match event {
Event::Join((uid, _tid)) => println!("user {:?} joined", uid),
Event::Leave((uid, tid)) => println!("user {:?} left {:?}", uid, tid),
Event::Message((_, _, msg)) => println!("broadcast: {}", msg),
}
if let Event::Message((_, _, msg)) = event {
println!("broadcast: {}", msg);
}
} 函数 function
trait
宏
错误处理
变量所有权
Copy
Borrow
fn main() {
let data = vec![1, 2, 3, 4];
let data1 = &data;
// 值的地址是什么?引用的地址又是什么?
println!(
"addr of value: {:p}({:p}), addr of data {:p}, data1: {:p}",
&data, data1, &&data, &data1
);
println!("sum of data1: {}", sum(data1));
// 堆上数据的地址是什么?
println!(
"addr of items: [{:p}, {:p}, {:p}, {:p}]",
&data[0], &data[1], &data[2], &data[3]
);
}
fn sum(data: &Vec<u32>) -> u32 {
// 值的地址会改变么?引用的地址会改变么?
println!("addr of value: {:p}, addr of ref: {:p}", data, &data);
data.iter().fold(0, |acc, x| acc + x)
}
// data1、&data 和传到 sum() 里 data1’ 都指向 data 本身,值的地址是固定的
// 引用的地址都是不同的,印证 Copy trait 的时候,介绍过只读引用实现了 Copy trait,也就意味着引用的赋值、传参都会产生新的浅拷贝 ![[../_static/principle_law.png]] 绕过“一个值只有一个所有者”限制
Rc<T>
Rc<Refcell<T>>
Arc<T>
Arc<Mutex<T>>
Arc<RwLock<T>> 生命周期 lifetime
![[../_static/value_lifecycle.png]] 内存管理
教程
Rust 编程第一课 · 陈天
前置知识篇回顾软件开发基础概念:堆、栈、函数、闭包、虚表、泛型、同步和异步等。学好任意一门编程语言,首先要吃透涉及概念,因为编程语言,不过是这些概念的具体表述和载体。 内存:值放堆上还是放栈上
![[../_static/mem_stack_struct.png]] 串讲:编程开发中需要掌握的基本概念
基础知识篇
cargo install cargo-edit
cargo add anyhow colored jsonxf mime
cargo add clap --allow-prerelease
cargo add reqwest --features json
cargo add tokio --features full
cargo build --quiet && target/debug/httpie httpbin.org/post post a=1 b=2
cargo run -- post https://httpbin.org/post a=1 b=2
../httpie/target/debug/httpie get "http://localhost:3000/image/CgoKCAjYBBCgBiADCgY6BAgUEBQKBDICCAM/https%3A%2F%2Fimages%2Epexels%2Ecom%2Fphotos%2F2470905%2Fpexels%2Dphoto%2D2470905%2Ejpeg%3Fauto%3Dcompress%26cs%3Dtinysrgb%26dpr%3D2%26h%3D750%26w%3D1260" 进阶篇
并发篇
实战篇
测试
代码统计
项目
教程图书
工具框架
参考
|
The First Rust Class #学习 Rust 的难点 #1. 精准学习 #前置篇 栈上存放的数据是静态的,固定大小,静态生命周期;堆上存放的数据是动态的,不固定大小,动态生命周期。 #栈 字符、整数、浮点数、布尔值、数组(array)、元组(tuple)、指针、引用、函数、闭包 结构体(structure type) -- struct #学习资料 #所有权和生命周期 Copy trait 与 Drop trait 不能共存。 #多个所有者 #内部可变性 #生命周期 #类型系统 #多态 #单态化 底层逻辑就是胖指针:数据本身+虚函数表 vtable; 如果 trait 所有的方法,返回值是 Self(trait object 产生时原来的类型会被抹去) 或者携带泛型参数(trait object 是运行时的产物),那么这个 trait 就不能产生 trait object。 rust会为实现了trait object类型的trait实现,生成相应的vtable,放在可执行文件中(一般在TEXT或RODATA段)。 #Traits send/sync: 如果一个类型 T: Send,那么 T 在某个线程中的独占访问是线程安全的;如果一个类型 T: Sync,那么 T 在线程间的只读共享是安全的; Clone 是深度拷贝,栈内存和堆内存一起拷贝; Copy 是按位浅拷贝,与 Drop 互斥; 不支持 Send / Sync 的数据结构主要有: 裸指针 *const T / *mut T。它们是不安全的,所以既不是 Send 也不是 Sync。 #延迟绑定 #智能指针: &mut[T] 可写的切片 Box<[T]> 堆上分配的切片: 而 Box<[T]> 一旦生成就固定下来,没有 capacity,也无法增长;对数据具有所有权。 Vec 可以通过 into_boxed_slice() 转换成 Box<[T]>,Box<[T]> 也可以通过 into_vec() 转换回 Vec; 当我们需要在堆上创建固定大小的集合数据,且不希望自动增长,那么,可以先创建 Vec,再转换成 Box<[T]> ; Box<[T]>和&[T]的区别: Box<[T]>指针指向的是堆内存数据;&[T]指针指向的数据可以是堆、栈内存数据; #哈希表 SIMD 查表 #错误处理的主流方法 闭包是一种匿名类型,一旦声明,就会产生一个新的类型(调用闭包时可以直接和代码对应),但这个类型无法被其它地方使用。这个类型就像一个结构体,会包含所有捕获的变量。 #Unsafe Rust 可以使用、也推荐使用 unsafe 的场景 实现 unsafe trait: 访问或者修改可变静态变量 一定要用注释声明代码的安全性 处理 FFI 的注意事项 如何处理数据结构的差异? Rust shim 主要做四件事情: 提供 Rust 方法、trait 方法等公开接口的独立函数。注意 C 是不支持泛型的,所以对于泛型函数,需要提供具体的用于某个类型的 shim 函数。 通过网络:REST API、gRPC 并发vs并行 并发和并行都是对“多任务”处理的描述,其中并发是轮流处理,而并行是同时处理。 在处理并发的过程中,难点并不在于如何创建多个线程来分配工作,在于如何在这些并发的任务中进行同步。 我们来看并发状态下几种常见的工作模式: 自由竞争模式、 #Mutex #RwLock 在实践中,Condvar 往往和 Mutex 一起使用:Mutex 用于保证条件在读写时互斥,Condvar 用于控制线程的等待和唤醒。 #Channel channels channels2 #Actor Atomic 在处理简单的原生类型时非常有用,如果你可以通过 AtomicXXX 结构进行同步,那么它们是最好的选择。 #Future tasks:待处理任务 使用 Future 的注意事项 我们要避免在异步任务中处理大量计算密集型的工作; #自引用数据结构 渐进式的架构设计,从 MVP 的需求中寻找架构的核心要素,构建一个原始但完整的结构(primitive whole),然后围绕着核心要素演进 分层结构、流水线结构和插件结构 |
捋捋 Rust 中的 impl Trait 和 dyn Trait 缘起 fn some_fn(param: SomeType) -> Button 随着开发进度增加, 这个函数需要返回 Button, TextView 等组件中的一个, 我下意识地写出了类似于下面的代码 fn some_fn(param1: i32, param2: i32) -> impl View {
if param1 > param2 {
// do something...
return Button {};
} else {
// do something...
return TextView {};
}
} 可惜 Rust 编译器一如既往地打脸, Rust 编译器报错如下
从编译器报错信息看函数返回值虽然是 impl View 但其从 if 分支推断返回值类型为 Button 就不再接受 else 分支返回的 TextView. 这与 Rust 要求 if else 两个分支的返回值类型相同的特性一致. 那能不能让函数返回多种类型呢? Rust 之所以要求函数不能返回多种类型是因为 Rust 在需要在 编译期确定返回值占用的内存大小, 显然不同类型的返回值其内存大小不一定相同. 既然如此, 把返回值装箱, 返回一个胖指针, 这样我们的返回值大小可以确定了, 这样也许就可以了吧. 尝试把函数修改成如下形式: fn some_fn(param1: i32, param2: i32) -> Box<View> {
if param1 > param2 {
// do something...
return Box::new(Button {});
} else {
// do something...
return Box::new(TextView {});
}
} 现在代码通过编译了, 但如果使用 Rust 2018, 你会发现编译器会抛出警告:
编译器告诉我们使用 trait object 时不使用 dyn 的形式已经被废弃了, 并且还贴心的提示我们把 Box 改成 Box, 按编译器的提示修改代码, 此时代码 no warning, no error, 完美. 但 impl Trait 和 Box 除了允许多种返回值类型的之外还有什么区别吗? trait object 又是什么? 为什么 Box 形式的返回值会被废弃而引入了新的 dyn 关键字呢? 埋坑 When code involves polymorphism, there needs to be a mechanism to determine which specific version is actually run. This is called ‘dispatch’. There are two major forms of dispatch: static dispatch and dynamic dispatch. While Rust favors static dispatch, it also supports dynamic dispatch through a mechanism called ‘trait objects’. 假设 Foo 和 Bar 都实现了 Noop 特性, Rust 会把函数 fn x(...) -> impl Noop 展开为 fn x_for_foo(...) -> Foo
fn x_for_bar(...) -> Bar (仅作原理说明, 不保证编译会这样展开函数名). 通过单态化, 编译器消除了泛型, 而且没有性能损耗, 这也是 Rust 提倡的形式, 缺点是过多展开可能会导致编译生成的二级制文件体积过大, 这时候可能需要重构代码. 静态分发虽然有很高的性能, 但在文章开头其另一个缺点也有所体现, 那就是无法让函数返回多种类型, 因此 Rust 也支持通过 trait object 实现动态分发. 既然 Trait 是具有某种特性的类型的集合, 那我们可以把 Trait 也看作某种类型, 但它是"抽象的", 就像 OOP 中的抽象类或基类, 不能直接实例化. Rust 的 trait object 使用了与 c++ 类似的 vtable 实现, trait object 含有1个指向实际类型的 data 指针, 和一个指向实际类型实现 trait 函数的 vtable, 以此实现动态分发. 更加详细的介绍可以在 Exploring Dynamic Dispatch in Rust fn some_fn(param1: i32, param2: i32) -> &'static View {
if param1 > param2 {
// do something...
return &Button {};
} else {
// do something...
return &TextView {};
}
} 我不喜欢添加额外的生命周期说明, 想必各位也一样. 所以我们可以用拥有所有权的 Box 智能指针避免烦人的生命周期说明. 至此 Box 终于出现了. 那么问题来了, 为什么编译器会提示 Box 会被废弃, 特地引入了 dyn 关键字呢? 答案可以在 RFC-2113 中找到. RFC-2113 明确说明了引入 dyn 的原因, 即语义模糊, 令人困惑, 原因在于没有 dyn 让 Trait 和 trait objects 看起来完全一样, RFC 列举了3个例子说明. 第一个例子, 加入你看到下面的代码, 你知道作者要干什么吗? impl SomeTrait for AnotherTrait impl<T> SomeTrait for T where T: Another 你看懂了吗? 说实话我也看不懂 : ) PASS 第二个例子, impl MyTrait {} 是正确的语法, 不过这样会让人以为这会在 Trait 上添加默认实现, 扩展方法或其他 Trait 自身的一些操作. 实际上这是在 trait object 上添加方法. 如在下面代码说明的, Trait 默认实现的正确定义方法是在定义 Trait 时指定, 而不应该在 impl Trait {} 语句块中. trait Foo {
fn default_impl(&self) {
println!("correct impl!");
}
}
impl Foo {
fn trait_object() {
println!("trait object impl");
}
}
struct Bar {}
impl Foo for Bar {}
fn main() {
let b = Bar{};
b.default_impl();
// b.trait_object();
Foo::trait_object();
} Bar 在实现了 Foo 后可以通过 b.default_impl 调用, 无需额外实现, 但 b.trait_object 则不行, 因为 trait_object 方法是 Foo 的 trait object 上的方法. 如果是 Rust 2018 编译器应该还会显示一条警告, 告诉我们应该使用 impl dyn Foo {} 第三个例子则以函数类型和函数 trait 作对比, 两者差别只在于首字母是否大写(Fn代表函数trait object, fn则是函数类型), 难免会把两者弄混. 更加详细的说明可以移步 RFC-2113 总结 引用 impl Trait for returning complex types with ease Traits and Trait Objects in Rust |
帮我分析一下这句命令:cargo run -- demo.rs --crate-type=lib,特别是后面的--crate-type=lib
这个命令是用于在 Rust 编程语言环境中运行一个程序。让我们逐部分分析这个命令:
总结:这条命令看起来是试图用 Cargo 运行一个 Rust 文件,同时指示这个文件应当以库的方式进行编译。这可能不是典型的用法,因为通常 |
在Rust中, 首先,你需要创建一个库项目,因为过程宏必须定义在它们自己的专用库中。这里我们将创建一个名为
这个例子展示了过程宏的基本用法:定义一个宏,它在编译时读取并操作Rust代码,然后在另一个项目中使用这个宏。过程宏是Rust中非常强大的工具,可以用于各种复杂的代码生成场景。 |
总结
C:\Users\cisen\AppData\Local\Temp
入门
- https://github.com/tyrchen/geektime-rust
rustup override set stable/nightly
rustup update stable
rustup component add clippy
rustup override set 1.50.0
,rustup override set 1.50.0-nightly
rustup override set nightly-2020-12-23
rustup target add wasm32-unknown-unknown
rustup self uninstall
为什么
优点
缺点
相关文章
The text was updated successfully, but these errors were encountered: