如另一个答案所示,尽可能使用预制包装器。在不存在此类包装器的情况下,以下指南可能会有所帮助。
返回Result 表示错误
包含错误信息的惯用 Rust 返回类型是 Result (std::result::Result)。对于来自 POSIX libc 的大多数函数,专用类型 std::io::Result 非常适合,因为它使用 std::io::Error 对错误进行编码,并且它包括由 errno 值表示的所有标准系统错误。避免重复的一个好方法是使用实用函数,例如:
use std::io::{Result, Error};
fn check_err<T: Ord + Default>(num: T) -> Result<T> {
if num < T::default() {
return Err(Error::last_os_error());
}
Ok(num)
}
包装 fork() 如下所示:
pub fn fork() -> Result<u32> {
check_err(unsafe { libc::fork() }).map(|pid| pid as u32)
}
Result 的使用允许惯用用法,例如:
let pid = fork()?; // ? means return if Err, unwrap if Ok
if pid == 0 {
// child
...
}
限制返回类型
如果修改返回类型以便只包含“可能”的值,则该函数将更易于使用。例如,如果一个函数在逻辑上没有返回值,但返回一个 int 只是为了传达错误的存在,那么 Rust 包装器应该什么都不返回:
pub fn dup2(oldfd: i32, newfd: i32) -> Result<()> {
check_err(unsafe { libc::dup2(oldfd, newfd) })?;
Ok(())
}
另一个例子是逻辑上返回无符号整数的函数,例如 PID 或文件描述符,但仍将其结果声明为有符号以包含 -1 错误返回值。在这种情况下,请考虑在 Rust 中返回一个无符号值,如上面的 fork() 示例所示。 nix 更进一步,让fork() 返回Result<ForkResult>,其中ForkResult 是一个真正的枚举,带有is_child() 等方法,并使用模式匹配从中提取PID。
使用选项和其他枚举
Rust 有一个丰富的类型系统,它允许表达必须在 C 中编码为魔法值的事物。回到 fork() 示例,该函数返回 0 表示子返回。这将自然地用Option 表示,并且可以与上面显示的Result 组合:
pub fn fork() -> Result<Option<u32>> {
let pid = check_err(unsafe { libc::fork() })? as u32;
if pid != 0 {
Some(pid)
} else {
None
}
}
此 API 的用户将不再需要与魔法值进行比较,而是会使用模式匹配,例如:
if let Some(child_pid) = fork()? {
// execute parent code
} else {
// execute child code
}
返回值而不是使用输出参数
C 经常使用 输出参数 来返回值,这是存储结果的指针参数。这要么是因为实际的返回值是为错误指示器保留的,要么是因为需要返回多个值,并且历史 C 编译器对返回结构的支持很差。
相比之下,Rust 的Result 支持独立于错误信息的返回值,并且返回多个值完全没有问题。作为元组返回的多个值比输出参数更符合人体工程学,因为它们可以在表达式中使用或使用模式匹配来捕获。
将系统资源包装在拥有的对象中
当返回系统资源的句柄(例如文件描述符或 Windows 句柄)时,最好将它们包装在实现Drop 的对象中以释放它们。这将降低包装器的用户出错的可能性,并且它使返回值的使用更加惯用,消除了对 close() 的尴尬调用以及因失败而导致的资源泄漏的需要。
以pipe()为例:
use std::fs::File;
use std::os::unix::io::FromRawFd;
pub fn pipe() -> Result<(File, File)> {
let mut fds = [0 as libc::c_int; 2];
check_err(unsafe { libc::pipe(fds.as_mut_ptr()) })?;
Ok(unsafe { (File::from_raw_fd(fds[0]), File::from_raw_fd(fds[1])) })
}
// Usage:
// let (r, w) = pipe()?;
// ... use R and W as normal File object
这个pipe() 包装器返回多个值并使用包装器对象来引用系统资源。此外,它返回 Rust 标准库中定义并被 Rust 的 IO 层接受的 File 对象。