开始
今天开始,为了切入webassembly,入坑Rust;
那么问题来了:为什么现在就要切入webassembly?为什么选择Rust,而不选择c/c++,go等语言?
正如同大家所知道的,webassembly还不是十分成熟,离应用到生产环境还是有一段不少的距离,那么有必要那么急着把精力投入到这个领域上面去吗(Vue3.0,React源码还想去看)但是个人看法现在正是时候了。
第一,webassembly大势所趋,有很多人和公司都跃跃欲试,特别是游戏领域;
第二,webassembly对于大部分前端(包括我)来说必然是要学习一门静态语言(如果是c/c++大牛可以忽略)需要一定时间学习积淀。
那么另外一个问题为什么不选择c/c++或者go等语言尼;c/c++几十年过去了生态依然很强,而且学习的资料也很多,应用广阔,但是有一个问题很重要就是历史包袱很重,个人无法在短时间内能够熟练运用;而go,java等语言,学习成本不算高,应用也很广,其实用来切入webassembly真的无往而不利,只是目前有一个问题就是要带自己的gc,在webassembly还没到完全体的时候,始终是一道坎,或许3到5年后这个问题也不成问题了。
那么Rust有什么优势尼:
- 年轻,历史包袱比较小
- 设计也比较严谨
- 工具链也比较人性化,cargo跟npm使用体验上说还是挺相似的
- 性能也不错,可以在后端搭配node,做一些性能优化点
但是学习成本还是挺高,介于c++和go语言之间(个人认为)。以上综合一下rust算是以一点微弱优势胜出,其实嘛,rust是火狐主推的语言,从长远看还是会无限贴近web的开发者,这也是它潜在的一个优势。
Rust相关中文资料
Rust 程序设计语言(第二版) 简体中文版
通过例子学Rust
Rust死灵书
Rust宏小册 中文版
Rust常用设计模式
rust Too Many Lists 通过实现链表学习rust
rust第一版文档 虽然不是最新但是反而比较深入细节
细说rust错误处理
rust reference
rust async book
以后有更多中文资料会持续更新
Rust迷思
temporary value is freed at the end of this statement
示例代码如下:
struct Cat {
pub c: i32
}
struct Bar {
pub b: i32
}
struct Foo<'a> {
bar: &'a Bar,
cat: &'a Cat
}
fn main() {
let bar: &Bar = &Bar{ b: 123 };
let cat: &Cat = &Cat{ c: 234 };
let foo: &mut Foo;
{
foo = &mut Foo{bar: bar, cat: cat}; //error
println!("{}", foo.cat.c);
}
}
直接执行就会报这样一个错误:
|
17 | foo = &mut Foo{bar: bar, cat: cat};
| ^^^^^^^^^^^^^^^^^^^^^^^- temporary value is freed at the end of this statement
| |
| creates a temporary which is freed while still in use
18 | println!("{}", foo.cat.c);
| --------- borrow later used here
|
= note: consider using a `let` binding to create a longer lived value
但是我把代码改变一下:
{
let foo = &mut Foo{bar: bar, cat: cat};
println!("{}", foo.cat.a);
}
这样是正常执行的;
但是第一种为什么跑不通尼,根据编译器提示,执行rustc --explain E0716,看一下编译器给的提示(果然rust是编译器教你写代码);
大概总结一下:
foo = &mut Foo{bar: bar, cat: cat};
大致等同于:
foo = {
//创建了一个tmp变量,拥有Foo实例的所有权
let mut tmp = Foo{bar: bar, cat: cat};
//返回tmp的引用
&mut tmp
}
tmp的生命周期明显只在花括号范围内,脱离之后就要释放,所以返回的是悬垂引用。
而使用let的时候,对于临时变量的引用,编译器会自动扩展临时变量的生命周期的。
又另外一种情况,把main函数的代码改一下:
fn main() {
let bar: &Bar = &Bar{ b: 123 };
let cat: &Cat = &Cat{ c: 234 };
let mut foo: Foo = Foo {bar: bar, cat: cat};
{
foo.cat = &Cat{c: 666};
println!("{}", foo.cat.c)
}
println!("{}", foo.cat.c);
}
这代码会正确运行吗,foo.cat引用了里面的一个临时变量;
答案是正确会运行的。
再改一下:
fn main() {
let bar: &Bar = &Bar{ b: 123 };
let cat: &Cat = &Cat{ c: 234 };
let mut foo: Foo = Foo {bar: bar, cat: cat};
{
//foo.cat = &Cat{c: 666};
let local_cat = &Cat{c: 666};
foo.cat = &local_cat;
println!("{}", foo.cat.c)
}
println!("{}", foo.cat.c);
}
答案还是会正确运行的。再改一下:
fn main() {
let bar: &Bar = &Bar{ b: 123 };
let cat: &Cat = &Cat{ c: 234 };
let mut foo: Foo = Foo {bar: bar, cat: cat};
{
//foo.cat = &Cat{c: 666};
let local_cat = Cat{c: 666};
foo.cat = &local_cat;
println!("{}", foo.cat.c)
}
println!("{}", foo.cat.c);
}
编译器终于有提示了,开始提示引用生命周期不够长了。究竟又是为什么尼,再看看rustc --explain E0716;直接贴吧:
Temporaries are not always dropped at the end of the enclosing
statement. In simple cases where the&
expression is immediately
stored into a variable, the compiler will automatically extend
the lifetime of the temporary until the end of the enclosing
block. Therefore, an alternative way to fix the original
program is to writelet tmp = &foo()
and notlet tmp = foo()
:
fn foo() -> i32 { 22 }
fn bar(x: &i32) -> &i32 { x }
let value = &foo();
let p = bar(value);
let q = *p;
Here, we are still borrowingfoo()
, but as the borrow is assigned
directly into a variable, the temporary will not be dropped until
the end of the enclosing block. Similar rules apply when temporaries
are stored into aggregate structures like a tuple or struct:
// Here, two temporaries are created, but
// as they are stored directly into `value`,
// they are not dropped until the end of the
// enclosing block.
fn foo() -> i32 { 22 }
let value = (&foo(), &foo());
也就是说除了let,struct和tuple都可以扩展临时变量的生命周期以免被释放。
所以我们如果用vec!的话还是会报同样的生命周期不够长的错误:
fn main() {
fn foo() -> i32 { 22 }
let v = vec!(&foo(), &foo());
println!("{:?}", v);
}
Struct的引用成员使用相同的生命周期标识符会有问题吗?
从文档学习的时候,如果struct含有引用类型的字段,都会强制要求你添加生命周期标识符。
struct Foo<'a> {
bar: &'a Bar
}
当然仔细想想也是很合理,因为要让编译器知道Foo实例最多能存活到什么时候,例如这里,Foo的生命周期肯定要比bar的引用要短或者一样长,否则就会出现悬垂引用了。那么当拥有两个引用类型的字段的时候,又会建议你这么做:
struct Foo<'a, 'b> {
bar: &'a Bar,
cat: &'b Cat
}
这里使用了‘a,’b两个生命周期标识符,分别标记bar和cat这两个引用的生命周期,当然这也是很合理,现在编译器知道Foo实例的生命周期肯定小于等于bar和cat两者中最小的生命周期了。
那么问题来了,如果这里只使用一个生命周期标识符‘a又会怎么样尼:
struct Foo<'a> {
bar: &'a Bar,
cat: &'a Cat
}
这时候‘a会等于替换成bar和cat两者引用最小的生命周期,这就是生命周期的协变性。
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。