(一)在这里,下面是(二)。html
写了一天的 Rust 代码下来,发现根本没写几行,万事开头难啊。仔细想来,多半功夫花在学习最佳实践和调试编译错误上了。说到这编译错误我就气不打一出来,放着好生生的 Python 不用跑这来糟心~~呵呵,开玩笑,Rust 的语法——尤为是对于内存管理——可谓是至关精密,在调好了许多编译错误以后,每每会发出“原来是这样”的感叹。对于初学者的我来讲,着实不该该指望着一上来就能顺风顺水地写出一次性编译经过的代码——固然我相信对语言的熟悉程度最终会致使这一结果。python
废话很少说了,聊一下碰到的一个问题和学习到的东西。以前大部分编译错误都能磕磕绊绊修过去学明白,直到碰到了这个问题(片断):git
pub fn parse_uri(uri: &str) -> Result<(&str, &str), int> { match uri.find_str("://") { Some(pos) => { let protocol = uri.slice_to(pos); let address = uri.slice_from(pos + 3); if protocol.len() == 0 || address.len() == 0 { Err(consts::EINVAL) } else { Ok((protocol, address)) } }, None => Err(consts::EINVAL), } }
这一段其实很简单,就是 ZMQ 里面解析一个 endpoint 的代码。好比给一个 endpoint tcp://127.0.0.1:8890
做为参数,正确解析的话就应该返回两个字符串:tcp
和 127.0.0.1:8890
。不得不用 Python 写一下示意:github
def parse_uri(uri): return uri.split('://', 1)
回到 Rust。我指望作到的就是在不拷贝字符串的状况下,经过 borrowed pointer aka reference 来实现字符串的解析切分,即借进去一个字符串的引用,返回来该字符串上不一样位置的两个不一样的引用。理想和现实的差距见下:编程
...rs:19:28: 19:31 error: cannot infer an appropriate lifetime for region in type/impl due to conflicting requirements ...rs:19 let protocol = uri.slice_to(pos); ^~~
貌似意思是说,在这个函数里面,Rust 编译器不能确保返回回去的引用到时候还能用。segmentfault
这下麻烦了。安全
怎么搞呢?仍是得看文档。书上说,这种状况下应该给生命期起个名字。数据结构
原来,Rust 对于每个引用的生命期都是严格控制的,在编译期就保证了引用的阳寿不会长过它引用的对象,从而确保了“野指针”的不可发生性。在刚才的代码中,对于新建立的两个引用 protocol
和 address
,编译器没有拿到足够的信息以说明它们俩不会变成“野指针”,故而出于安全报了错误。修好这个错误异常简单:app
- pub fn parse_uri(uri: &str) -> Result<(&str, &str), int> { + pub fn parse_uri<'r>(uri: &'r str) -> Result<(&'r str, &'r str), int> {
这个长的像泛型的东西就叫作命名生命期,就是一个单引号 '
加随便一个名字。尖括号里的能够认为是声明,就是说当前这个函数将会涉及到一个叫作 r
的生命期;参数里面出现的算是定义,好比说这个例子里面,uri: &'r str
的意思就是说,uri
是一个字符串引用,生命期 r
就恰好跟 uri
的同样;最后返回值类型里面就是使用这个生命期的地方,意思就是说,这个引用和这个引用的声明期跟 r
一致。这样一来,咱们就把编译器缺失的信息补上了。tcp
值得注意的是,并非全部返回的引用都能像这样套用生命期定义,只有从参数里的引用派生出来的引用才能够——好比说这样是不行的:
fn test_ref<'r>(input: &'r str) -> &'r int { let num = ~123; &*num }
编译结果:
t.rs:3:5: 3:10 error: borrowed value does not live long enough t.rs:3 &*num ^~~~~ t.rs:1:44: 4:2 note: reference must be valid for the lifetime &'r as defined on the block at 1:43... t.rs:1 fn test_ref<'r>(input: &'r str) -> &'r int { t.rs:2 let num = ~123; t.rs:3 &*num t.rs:4 } t.rs:1:44: 4:2 note: ...but borrowed value is only valid for the block at 1:43 t.rs:1 fn test_ref<'r>(input: &'r str) -> &'r int { t.rs:2 let num = ~123; t.rs:3 &*num t.rs:4 }
上次说到不用 box 或是引用的参数传递是内存拷贝,这确实是这样,但若是数据量比较小,官方仍是建议直接作内存拷贝,而不是使用引用或 box ——为了细微的性能提高而增长代码的复杂性是不值得的;除非必要,若是怕影响性能,能够先作个性能测试。
相反,对于返回值,相似的“值传递”却能在强大的编译器的帮助下“变成”零拷贝。改自官方例子:
fn foo(x: ~int) -> int { return *x; } fn main() { let x = ~5; let y = ~foo(x); }
main()
先建立一个 owned box,放进去一个 5
,而后把这个 box 扔进 foo()
里,foo()
把 box 打开扔掉,把内容 5
返回,最后在 main()
里再给 5
套一个盒子 y
。
问:这个过程当中,5
一共被拷贝了几回?
两次?foo()
里拆包一次,返回以后套盒子又一次?
只有一次!原来,在 foo()
返回以前,y
的盒子空间就已经准备好了,foo()
作的其实只是把 5
从一个盒子拷贝到另外一个盒子。听说这都是编译器干的好事。
因此呢,官方文档建议你们,在返回数据结构的时候,大胆返回整个值就行了,不用考虑返回前先放到盒子里(用 ~
),让调用的地方灵活地来选择是应该放盒子仍是怎么样。
result
想必你们都知道,Rust 没有提供空指针,也没有提供异常处理。那那那,这代码还怎么写啊,人家明天还要上班呢!
其实前面已经有一个例子了,就是 parse_uri
——它会把一个 ZMQ 的地址拆成两部分,不然报错。这里咱们看看怎么调用它:(摘自刚写的 zmq.rs 的 bind
)
fn bind(&self, addr: &str) -> Result<(), int> { parse_uri(addr).and_then(|(protocol, address)| { match protocol { "tcp" => Ok(()), _ => Err(consts::EINVAL), } }) }
哈哈,其实很简单明了嘛。and_then
这里起到了承上启下的做用,意思是“先解析 URI,而后返回(执行)下面的;除非解析 URI 失败了,跳过下面直接返回错误”,大大地体现了函数式编程的特色有没有。这个 and_then
其实就是日常有异常的语言中的顺序执行嘛,不去抓异常,依次执行,只要出错就跳出,没错就执行到底。
简单总结一下哈。
Result
枚举有两种值,Ok
和 Err
,适合于可能出错的函数来返回。
Option
枚举也有两种值,Some
和 None
,适合于意义上须要返回“虚无”的函数。
Condition
好像给删掉了?
它们都有一堆很方便的小函数能够用,请参考上述文档连接。