Rust 语言受限的地方
2020-04-18 10:03 rust
最近半年陆续写了很多的 Rust 代码,跟别的语言相比,它有些地方还是蛮受限的,在表达 一个功能时需要各种弯弯绕,下面就列举几个方面的问题。
struct/union 不支持嵌套
类型嵌套可以将关联的数据结构放在一起,这个在C语言里是很常用的。像内核提供的系统
调用,比如 bpf()
以及 io_uring_setup()
,都有大量的使用。它们都是定义了一堆的
操作指令(operation-code, op-code),以及一个公用的 union
联合体,跟据不同的
操作指令来解析 union
为不同的结构。尤其是 bpf()
系统调用,使用一个接口就实现了
内核 BPF
虚拟机的所有操作,相当的强大和复杂。
在使用Rust为类似这样的 C
接口做绑定时,会很麻烦。需要先将嵌套的结构体以及 union
联合体拆成独立的片段,然后再依次组装到一起,这样写的代码,其可读性并不好,也更难
维护。
/// IO submission data structure (Submission Queue Entry)
#[repr(C)]
#[derive(Copy, Clone)]
pub struct io_uring_sqe_t {
/// type of operation for this sqe
pub opcode: IOURING_OP,
/// IOSQE_ flags
pub flags: u8,
/// ioprio for the request
pub ioprio: u16,
/// file descriptor to do IO on
pub fd: i32,
pub file_off: io_uring_sqe_file_off_t,
pub buf_addr: io_uring_sqe_buf_addr_t,
/// buffer size or number of iovecs
pub len: u32,
pub other_flags: io_uring_sqe_other_flags_t,
/// data to be passed back at completion time
pub user_data: u64,
pub opt_buf: io_uring_sqe_opt_buf_t,
}
以上代码片段定义了 IO_Uring
里的 Submition Queue Entry
,这里需要额外定义4个
结构体/联合体;而在原先的内核头文件中,它们是放在了一起定义的,很明确:
/*
* IO submission data structure (Submission Queue Entry)
*/
struct io_uring_sqe {
__u8 opcode; /* type of operation for this sqe */
__u8 flags; /* IOSQE_ flags */
__u16 ioprio; /* ioprio for the request */
__s32 fd; /* file descriptor to do IO on */
union {
__u64 off; /* offset into file */
__u64 addr2;
};
union {
__u64 addr; /* pointer to buffer or iovecs */
__u64 splice_off_in;
};
__u32 len; /* buffer size or number of iovecs */
union {
__kernel_rwf_t rw_flags;
__u32 fsync_flags;
__u16 poll_events;
__u32 sync_range_flags;
__u32 msg_flags;
__u32 timeout_flags;
__u32 accept_flags;
__u32 cancel_flags;
__u32 open_flags;
__u32 statx_flags;
__u32 fadvise_advice;
__u32 splice_flags;
};
__u64 user_data; /* data to be passed back at completion time */
union {
struct {
/* pack this to avoid bogus arm OABI complaints */
union {
/* index into fixed buffers, if used */
__u16 buf_index;
/* for grouped buffer selection */
__u16 buf_group;
} __attribute__((packed));
/* personality to use, if used */
__u16 personality;
__s32 splice_fd_in;
};
__u64 __pad2[3];
};
};
这样的接口是很难维护的。
不支持类型继承(subtyping)
Rust 本身支持基于 Trait
的继承,比如:
pub trait Animal {
}
pub trait Cat: Animal {
}
但是,与 java/python 等相比的话,并不支持基于结构体的继承,就像下面的写法。
pub struct Animal {
}
pub struct Cat: Animal {
}
社区里更推荐使用组合的写法,即所谓的“组合优于继承”。
不支持尾递归优化(Tail Call Optimization)
尾递归调用对于一些算法的实现是更直接的,更易理解。一些编译器和语言支持对这样的
尾递归调用做优化,在生成的代码中,并不会创建新的函数调用栈(function frame),
其栈大小并不会增加,而只是更改寄存器的值,跳转(jump)到函数开始处。
但 Rust 并不支持对其优化,这个不是编译器的问题,rustc
的后端是 LLVM
,
LLVM 本身是支持尾递归优化的。
不支持 yield
这种语法是所谓的生成器模式(Generator),这个在 python 中很容易实现。有些算法
基于这个来实现的话,是很直接的,比如很出名的 Fabonacci
算法:
def fibonacci_init():
a, b = 0, 1
yield b
while True:
a, b = b, a+b
yield b
fib = fibonacci_init()
for _i in range(100):
print(next(fib))
但如果要在 Rust 中实现类似的操作,需要基于 Iterator
trait 实现。
#[derive(Debug)]
pub struct Fibonacci {
a: u64,
b: u64,
}
impl Fibonacci {
fn new() -> Fibonacci {
Fibonacci { a: 0, b: 1 }
}
}
impl Iterator for Fibonacci {
type Item = u64;
fn next(&mut self) -> Option<Self::Item> {
let tmp = self.b;
self.b += self.a;
self.a = tmp;
Some(tmp)
}
}
fn main() {
let fib = Fibonacci::new();
for n in fib.take(50) {
println!("> {}", n);
}
}