Rust入门笔记 —— 函数,所有权与复合类型
1 结构体与枚举
1.1 结构体
先讲结构体是因为后面方法需要结构体做支撑,其实这种用例子解释语法的方式,还是需要学过其他语言。因为前期肯定会用到后面才讲的东西
结构体主要有三种类型
- tuple :
struct Pair(i32, f32)
经典的 C struct,当然,结构体也是可以复合的
struct Point { x: f32, y: f32 }
- 单元(Unit)结构体:没有字段
结构体和 C 一样,用.+field_name引用变量
let point: Point = Point{ x: 10.3, y: 0.4 }; // 这里的 x 和 y 并不能省略
println!("coord : {}, {}", point.x, point.y);
let point_ext: Point = Point{ x: 1.2, ..point}; // 使用这种方法继承字段
let pair = Pair(1, 0.1) // 元组类型的就可以省略啦(不省略也没名字)
1.2 枚举
用 enum 关键字声明一个枚举,相比与 C,rust 的枚举有更多功能,任何对于结构体合法的内容也可以用于 enum
enum WebEvent {
PageLoad,
PageUnload,
// Unit
KeyPress(char),
Paste(String),
// Tuple
Click { x: i64, y: i64},
// 标准的结构
}
// 使用这种 match 结构就可以引用枚举字段及其内容
fn inspect(event: WebEvent) {
match event {
WebEvent::PageLoad => println!("page loaded"),
WebEvent::PageUnload => println!("page unloaded"),
// Destructure `c` from inside the `enum` variant.
WebEvent::KeyPress(c) => println!("pressed '{}'.", c),
WebEvent::Paste(s) => println!("pasted \"{}\".", s),
// Destructure `Click` into `x` and `y`.
WebEvent::Click { x, y } => {
println!("clicked at x={}, y={}.", x, y);
},
}
}
2 函数
2.1 函数与返回值
Rust的函数以 fn fn_name(arg_list) -> ret_type {body}
定义,函数的最后一行是返回值
Rust函数名称风格是snake_case(区别于Go的CamelCase)
Rust不要求被调用的函数必须在调用者之前定义,只要有定义即可
Rust的函数返回值可以是函数体尾部的表达式,也可以显式的使用return语句
返回()类型的函数,返回值类型声明可以省略
fn main() {
// 不要修改下面两行代码!
let (x, y) = (1, 2);
let s = sum(x, y);
assert_eq!(s, 3);
}
fn sum(x : i32, y:i32) -> i32 { // original : (x, y:i32) {
return x + y; // original : x + y;
}
上述答案中第10行可以是 x + y
,可以是return x + y;
,但是不能是 x + y;
因为分号会让表达式变为语句,语句的值是()
// 用两种方法求解
fn main() {
never_return();
}
fn never_return() -> ! {
// 实现这个函数,不要修改函数签名!
}
这题没有想到答案,两种方法分别可以是
fn never_return() -> ! {
panic!("I return nothing!")
}
fn never_return() -> ! {
loop{}
}
其实感觉有点扯淡
还有类似于成员函数的概念:把函数和类型加以关联,Rust 中用关联函数和方法实现,我门后面再讲
2.2 函数体表达式
我斗胆猜测这就是 Rust 中匿名函数的表现,Rust 使用大括号来编写一个较为复杂的表达式
let y = {
let x = 3;
x + 1
};
// y == 4
可以看到,大括号中的内容和函数中的内容一致,但是函数体表达式不能使用 return
3 所有权、引用和借用
C 使用手动管理内存,开发者申请释放,这常常造成资源浪费和内存泄漏。
Java 的 JVM 会负责在运行时回收内存,然而 GC 会导致运行效率的下降。
Rust 采用所有权机制,让它在编译阶段能更有效的分析内存资源。
- Rust 中的每个值都有一个变量,称为其所有者
- 一个值同时只能有一个所有者
- 当所有者不在程序运行范围内时,该值会被删除
3.1 变量范围
{
// 无效,未声明
let s = "str";
// 可用
}
// 无效
上面这段代码描述了 s 这个变量的作用范围,它代表变量的可行域,默认从声明变量开始有效,直到变量域结束。
变量和数据的交互方式主要有移动 Move 和克隆 Clone
3.2 所有权
移动与克隆
移动,即直接复制,对于基本数据类型(bool,整形、浮点等)Rust 会在栈上复制一份。
let x = 5;
let y = x;
在这样的操作后,栈上会有两个 5
但是如果数据在堆上,只会复制指针,类似于浅拷贝。
let s1 = String::from("str");
let s2 = s1;
println!("{}", s1); // Wrong! borrow of moved value: `s1`
当 s2 被绑定到堆上的 "str" 时,s1 就已经失效。
为了降低程序运行成本,默认的情况下非基本类型采用移动,但是如果需要复制一份数据,需要使用克隆
let s1 = String::from("str");
let s2 = s1.clone();
println!("{}, {}", s1, s2);
此时堆中会有两个 str,分别绑定给 s1 和 s2,释放的时候也会分别释放。
对于练习:
fn main() {
// 使用尽可能多的方法来通过编译
let x = String::from("hello, world");
let y = x;
println!("{},{}",x,y);
}
// 显然,编译会因为 x 失效而失败
// Sol1 :
let y = x.clone();
// Sol2 :
let x = "hello, world"; // 在栈上,会移动
所有权转移的时候,可变性也会跟着改变
fn main() {
let s = String::from("hello");
let mut s1 = s;
s1.push_str(" world");
println!("{}", s1);
}
涉及函数的情况
调用函数传入的参数,其所有权会被传递给 callee,因此会失效。但是基本类型不会失效,道理和上面一样。
换句话说,传参和移动的效果是一样的。
// 不要修改 main 中的代码
fn main() {
let s1 = String::from("hello, world");
let s2 = take_ownership(s1);
// s1 在此处失效
println!("{}", s2);
}
// 只能修改下面的代码!
fn take_ownership(s: String) {
println!("{}", s);
}
// Sol1
fn take_ownership(s: String) -> String {
println!("{}", s);
s
}
对于返回值,其所有权会被移动出函数,返回到调用函数的地方,不会被释放
fn main() {
let s = give_ownership();
println!("{}", s);
}
// 只能修改下面的代码!
fn give_ownership() -> String {
let s = String::from("hello, world");
// 将 String 转换成 Vec 类型
let _s = s.into_bytes();
s
}
// sol1 :
let _s = s.clone().into_bytes();
// sol2 :
let _s = s.as_bytes();
上面这道题中,s 的所有权在into_bytes方法中被移动,因此不能在返回处使用。但是修改之后,s 的所有权被移交回 main,所以 main 中可以使用。
引用和租借
引用类似于取指针,和 C++ 里面的引用感觉差不多
fn main() {
let s = String::from("hello, world");
print_str(&s);
// print_str(s);
println!("{}", s);
}
fn print_str(s: &String) {
// fn print_str(s: String) {
println!("{}",s);
}
在变量的值被引用的时候,变量不会被认定为无效。
引用不会获得值的所有权,只会 租借 值的所有权,未显式声明可变租借的所有权不可以修改数据
引用本身也是一个类型,它存的值其实就是被引用的值的地址。
fn main() {
let s1 = String::from("hello");
let s2 = &s1;
let s3 = s1;
println!("{}", s2);
}
// 错误,s2 只租借了 s1 的所有权,当 s1 的所有权失效,s2 租借的也失效了
fn main() {
let s1 = String::from("run");
let s2 = &s1;
println!("{}", s2);
s2.push_str("oob"); // 错误,禁止修改租借的值
println!("{}", s2);
}
fn main() {
let mut s1 = String::from("run");
// s1 是可变的
let s2 = &mut s1;
// s2 是可变的引用
s2.push_str("oob");
println!("{}", s2);
}
可变引用不允许多重引用,但是不可变引用可以。
评论 (0)