Rust & Zig 猜数字
分类:技术
以前囫囵吞枣学了一下 Rust 以及 Zig,但忘得快差不多了。最近有空闲准备再重新看一下。
迫使我有重新学习使用 Rust / Zig 的原因,主要还是 CMakeLists.txt 写起来太麻烦了。虽然 C/C++ 也有 xmake 这样用起来更加方便的工具,但对于我个人来说,总觉得味道怪怪的,用第三方库也不甚方便。
昨天把 The Rust Programming Language 里的猜数字游戏用 Rust 和 Zig 都写了一下。
Rust 版本:
use rand::Rng;
use std::cmp::Ordering;
use std::io;
fn main() {
println!("Guess the number!");
let secret_number = rand::thread_rng().gen_range(1..=100);
loop {
println!("Please input your guess.");
let mut guess = String::new();
io::stdin()
.read_line(&mut guess)
.expect("Failed to read line");
let guess: u32 = match guess.trim().parse() {
Ok(num) => num,
Err(_) => continue,
};
println!("You guessed: {guess}");
match guess.cmp(&secret_number) {
Ordering::Less => println!("Too small!"),
Ordering::Greater => println!("Too big!"),
Ordering::Equal => {
println!("You win!");
break;
}
}
}
}
Zig 版本:
const std = @import("std");
pub fn main() !void {
const stdout = std.io.getStdOut().writer();
const stdin = std.io.getStdIn().reader();
try stdout.print("Guess the number!\n", .{});
var prng = std.rand.DefaultPrng.init(blk: {
var seed: u64 = undefined;
try std.os.getrandom(std.mem.asBytes(&seed));
break :blk seed;
});
const secret_number = prng.random().intRangeAtMost(i32, 1, 100);
try stdout.print("The secret number is: {}\n", .{secret_number});
while (true) {
try stdout.print("Please input your guess.\n", .{});
var buffer: [128]u8 = undefined;
const guess_line = stdin.readUntilDelimiter(&buffer, '\n') catch {
@panic("Failed to read line");
};
const guess = std.fmt.parseInt(i32, std.mem.trim(u8, guess_line, &std.ascii.whitespace), 10) catch {
continue;
};
try stdout.print("Your guess is: {}\n", .{guess});
if (secret_number > guess) {
try stdout.print("Too small!\n", .{});
} else if (secret_number < guess) {
try stdout.print("Too big!\n", .{});
} else {
try stdout.print("You win!\n", .{});
break;
}
}
}
写完感觉挺符合我对它们俩的预期的,一个属于更好的 C++、一个属于更好的 C。
在这个例子里,虽然 Zig 的代码更加 explicit,但是我觉得写起来更舒服,原因有两点:
首先,必须显式进行错误处理。有出错机会的函数调用都需要使用 try
把错误抛给上层,或者使用 catch
原地处理。
其次,需要显式指定内存分配规则。比如 readUntilDelimiter
只能接受固定大小的缓冲,否则就需要换成自动分配内存的版本 readUntilDelimiterAlloc
,而这个版本也需要你传入允许分配的内存上限作为参数。所以你会意识到,允许输入「无限长」的字符串是没有意义、甚至是危险的,需要定一个合理的上限,从而开始考虑修改业务逻辑的设计。
当然,这个例子显然非常基础,还无法体现出内存安全的必要性。
以上。