rust语法细节讨论

语法

入门中文教程链接:

https://kaisery.github.io/trpl-zh-cn/ch02-00-guessing-game-tutorial.html

字符串的字节与字符

字节是u8,一个字节;字符是unicode编码,占4个字节。

字符串的字节序列用as_bytes()返回,字符序列用chars()返回。

字符串的len()返回的是字节数,而非字符数。字符数用s.chars().count()来获得。

字符串拼接

用+可以,但推荐使用format!宏。例如:

let s = format!("position:{} not recognized", pos)

常量

常量在编译时被求值,它们的值会在使用的地方被内联。

常量初始化时不能调用非const方法,否则报错:

calls in constants are limited to constant functions, tuple structs and tuple variants

原因也是因为const是编译期求值,当然只能调用编译期的const函数了。

const函数

与C++的const方法含义完全不同,C++里const函数的意思是,不修改其成员变量。

但rust里const方法指的是编译时执行的函数 ,所以里面是不能有for循环的。

全局静态变量

当我们用非const函数初始化全局static变量时,也会报与const类似的错误:

calls in statics are limited to constant functions, tuple structs and tuple variants

可见,static与const一样,也是一个编译期的概念,区别仅在于前者会在编译期分配存储空间,而后者不会。

但这种行为就跟C++或java里的全局静态变量区别很大了,后者实际是在运行期初始化的(确切的说,是在程序启动时)。因为实际使用中,很多全局static变量的初始化难以做到编译期就确定、也很难不依赖非const函数。

所以,rust里的全局static变量根本就不是我们想要的。那么,有没有等价替代物呢?

有,用lazy_static!宏:

lazy_static! {
        static ref RESERVED: Mutex<HashMap<String, TokenKind>> = Mutex::new(HashMap::from([
                ("and".to_string(), TokenKind::AND),
                ("or".to_string(), TokenKind::OR),
                ("div".to_string(), TokenKind::DIV),
                ...
            ]));
    }

加锁是防止多线程并发访问出错。我们要这样访问:

RESERVED.lock().unwrap()
.get(m.to_lowercase().as_str())
.copied()
.unwrap_or(TokenKind::ID)

数组与Vec

跟C++一样,数组长度编译期指定,所以必须要指定常量长度。

Vec类似于C++的vector,可以自动扩展。而且有vec!宏,写起来跟数组很类似。

hashmap

字面量初始化

hashmap的字面量初始化,使用HashMap::from方法,入参是个数组:

let reserved: HashMap<&str, TokenKind> = HashMap::from([
                ("and", TokenKind::AND),
                ("or", TokenKind::OR),
                ("div", TokenKind::DIV),
                ...
            ]);

不用一个个insert的调用。

带默认值的键查询

get with default的写法:

reserved.get(m.to_lowercase().as_str()).copied().unwrap_or(TokenKind::ID)

get返回的是一个Option<&V>,所以,需要用copied转出Option<V>,最后再用unwrap_or获取键不在时的默认值。

正则表达式

find和capture区别

像python里,提供了几种能力:match从头开始匹配,search从任意位置开始匹配,两者都只匹配一次;findall、finditer则找到所有匹配。且,python并不区分是普通模式还是分组捕获模式(capture),后者仅仅是返回的MatchObject对象支持group(1)、group(2)…方法来获取捕获的分组值。

rust里则无此种能力,find等价于python的search,从任意位置开始匹配,且只匹配一次;find_iter找到所有匹配。但find和find_iter都只对应于普通模式,要做分组捕获,得使用captures方法。

样例如下:

#[test]
fn test_reg_find() {
    let reg = Regex::new("create\\s+table\\s+(\\w+)");
    let mo = reg.unwrap().find_at("create table t_a", 0).unwrap();
    assert_eq!(mo.as_str(), "create table t_a");
}

fn test_reg_capture() {
    let reg = Regex::new("create\\s+table\\s+(\\w+)");
    let co = reg.unwrap().captures_at("create table t_a", 0).unwrap();
    assert_eq!(co.get(0).unwrap().as_str(), "create table t_a");
    assert_eq!(co.get(1).unwrap().as_str(), "t_a");
    assert_eq!(co.len(), 2);
}

find与find_at

我一开始使用的时候,对find_at的理解错误了,我以为find_at(str, n)等价于find(str[n…]),但实际不是的,看看下面例子:

let s = "  #   ";
let pat = Regex::new("^#").unwrap();
assert_eq!(pat.find(&s[2..]).unwrap().as_str(), "#");
assert_eq!(pat.find_at(s, 2), None);

find_at正如它注释里讲的,它会考虑正则表达式的上下文,这里的上下文就是^,它表示要从整个字符串s的最开始处匹配,即使我们指定了偏移量=2,事实上,它是无效的。

所以,我个人对于提供find_at的动机不太理解,作为API来讲,感觉很容易被误解。

null

rust没有空指针的概念,只能用Option的枚举None。

判断一个对象是否None有两种写法:==或is_none

#[test]
fn test_none() {
    let tk: Option<TokenKind> = None;
    assert!(tk == None);
    assert!(tk.is_none());
}

异常

rust没有异常,只有panic。像unwrap方法一般都会触发panic,panic会导致程序coredump,所以,除了在UT里,一般不建议使用unwrap,而要用match或if let的替代写法,当然,后者写起来比unwrap繁琐多了。

赋值语句

rust里的对象,用引用传递(rust术语叫borrow)没问题,用值传递会导致所有权转移,本质上就是C++里的auto_ptr。所以,看似普通的赋值语句也会像auto_ptr那样带来销毁式拷贝(destruction copy)的效果:

#[test]
fn test_assign() {
    let r1 = Rectangle {
        width: 1,
        height: 2
    };

    // value moved here
    let r2 = r1;             

    // value borrowed here after move
    println!("{}", r1.width);
    println!("{}", r2.width);
}

对于enum类型,也是这个结论,因为enum本质上也是一个对象。

但对于基本类型(如数字)及字符串字面量(&str)类型,赋值语句并不触发所有权转移,是没有问题的。比如下面代码可以正常执行:

#[test]
fn test_assign() {
    
    let i = 1;
    let j = i;
    println!("{}", i);
    println!("{}", j);
    
    // 这里一旦写成 let s = "abc".to_String();就会编译报错了
    let s = "abc";
    let s1 = s;
    println!("{}", s);
    println!("{}", s1);
}

Option::unwrap

注意Option::unwrap的定义:

pub const fn unwrap(self) -> T {
        match self {
            Some(val) => val,
            None => unwrap_failed(),
        }
    }

传入的是值self,而不是通常的引用&self。所以,unwarp调用完之后,原本的Option对象就消失了,不能再被访问了。亦即unwrap是一次性的调用方法。

闭包与函数指针

闭包就是匿名函数,可以捕获上下文。类似于其它语言的lambda。它有三个trait(FnFnMutFnOnce)。

函数指针(fn,注意f小写)实现了所有三个闭包 trait(FnFnMutFnOnce),所以总是可以在调用期望闭包的函数时传递函数指针作为参数。

亦即,函数指针就是闭包。

但,闭包不一定是函数指针。闭包只有在不引用upvalue的时候,才可以被自动转型为函数指针,否则会报错:

closures can only be coerced to `fn` types if they do not capture any variables

注意:fn是类型,Fn是trait,fn可以用作泛型参数,而Fn则必须加上dyn关键字才可用作泛型参数

闭包作为Vec元素

由前可知,Fn是trait,所以Vec类型参数里不能直接写Fn,但可以写fn。此时只要传入的闭包元素不引用upvalue,编译器会自动将fn强转Fn的。但若引用了upvalue,指定Vec类型参数为Fn,像这样:

let v: Vec<dyn Fn(&str) -> usize> = vec![|s| s.len(), |s| s.len()+x];

就会报错:

the size for values of type `dyn for<'a> Fn(&'a str) -> usize` cannot be known at compilation time

此时只能使用Box来解决trait object作为Vec类型参数无法计算size的问题,这么写即可:

let v: Vec<Box<dyn Fn(&str) -> usize> > = vec![Box::new(|s| s.len()), Box::new(|s| s.len()+x)];
for item in v {
	println!("{}", item("abc"));
}

这里特别说明一点:声明时必须明确指定Box<dyn Fn(&str) -> usize>类型作为Vec元素,否则依赖rust的自动类型推断,又会报错:

let v: Vec<_> = vec![Box::new(|s:&str| s.len()), Box::new(|s:&str| s.len()+x)];

这里未强制指定Vec元素类型,rust会报错:

expected closure, found a different closure

因为rust认为

|s:&str| s.len()
|s:&str| s.len()+x)

是两种类型的闭包,尽管两者的入参和返回值是一样的。

或者这样写也是可以的,就是难看点:

let v: Vec<_> = vec![Box::new(|s:&str| s.len()) as Box<dyn Fn(&str) -> usize>, Box::new(|s:&str| s.len()+x)];

这里把第一个闭包强转为Box<dyn Fn(&str) -> usize>,给rust类型推断一个指引,让它知道Vec的元素到底是啥,后面的闭包就不用再强转了。

struct构造

struct的构造器写起来有点麻烦,正常是要带字段名。不过,如果入参同字段名相同,也是可以省略掉字段名的。

我们一般会封装静态构造方法来简化struct的构造。不过rust不支持函数重载,所以不同参数个数的静态构造器得起不同的名字,这点比较麻烦(或者用builder模式?)。

  • 15
    点赞
  • 18
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值