TimothyQiu's Blog

keep it simple stupid

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,而这个版本也需要你传入允许分配的内存上限作为参数。所以你会意识到,允许输入「无限长」的字符串是没有意义、甚至是危险的,需要定一个合理的上限,从而开始考虑修改业务逻辑的设计。

当然,这个例子显然非常基础,还无法体现出内存安全的必要性。

以上。

RustZig

添加新评论 »