3

John Fremlin's blog: Review of Rust in 2022

 1 year ago
source link: http://john.freml.in/review-of-rust-2022
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

John Fremlin's blog: Review of Rust in 2022

Waiting for updates: connected

Posted 2022-11-25 23:00:00 GMT

The programming language Rust has grown up and is making waves, powering parts of the Linux kernel. The main innovation is the borrow checker which keeps track of references and which code is allowed to mutate which state.

I last tried it five years ago and definitely didn't have a good time as syntax was unstable so code snippets in old documentation didn't work. There was a bit of that still I couldn't use Tokio because the Tokio subcrate I wanted wasn't compatible with the new Tokio, and the error message was not helpful: "there is no reactor running."

The integration with VS Code allows context aware symbol completion and even correctly highlights issues with macros though the editor spins constantly at 100% CPU. Everything needs to be installed consistently through rustup (the Rust package manager, which doesn't collaborate with the Ubuntu package manager) or else the rust-analyzer gets infuriated.

The experience with halfbaked crates felt leftpaddy so I decided to jus call libc. This brought up Rust's apparent deliberate hostility to C. It is painful to call C functions correctly. Rust has reluctant support for errno (std::io::Error::last_os_error().raw_os_error()) and nul-terminated CStrings. I didn't see a code-snippet example of anybody handling EAGAIN in Rust to retry a syscall. Surely there must be at least one? Here's my first try

macro_rules! syscall {
    ($e: expr) => {{
        let mut unsafe_retry_ret;
        let start = Instant::now();
        loop {
            unsafe_retry_ret = unsafe { $e };
            if start.elapsed() > Duration::from_secs_f64(2.0) {
                eprintln!(
                    "system call took too long {} = {}: {:?}",
                    std::stringify!($e),
                    unsafe_retry_ret,
                    start.elapsed()
                );
            }
            if unsafe_retry_ret == -1 {
                if std::io::Error::last_os_error().raw_os_error() == Some(libc::EAGAIN) {
                    continue;
                };
                panic!(
                    "system call failed {}: {}",
                    std::stringify!($e),
                    std::io::Error::last_os_error()
                );
            }
            break;
        }
        unsafe_retry_ret
    }};
}

The macro system is pretty awesome and easy to use, even for local functions, where it allows a way of writing closures that can mutate while only borrowing at their invocation site. It's surprising that Rust hasn't implemented decltype or typeof.

It's nice to be able to put tests into the same file with the mod tests idiom, and the rustup target for static linking (cargo build --target=x86_64-unknown-linux-musl) worked perfectly.

My main notes were on interoperability:

  • Rustup and the import system certainly solve the biggest pain with C++, but C++ has great interoperability with C. So I think just for this reason Rust isn't easy to recommend for most projects, where there is existing software to interface with.
  • It would be nice if there were grades of unsafe, e.g. you might be able to call a C function but have the arguments checked for being nul-terminated strings or not.
  • The style pushes you towards a lot of panic! crash-handling which feels much less friendly than throwing exceptions (where the caller could decide whether the whole system should crash) or Golang where tiresome error checking is the idiom.
  • The friendly hygienic macros and discipline of the borrow checker make it pretty good for embedded programming, where you often want to generate repetitious code and keep track of who is allowed to change what.
  • Lack of implicit conversion between u8 and i8 and char lead to overly explicit naggy code. Probably C/C++ was too generous in implicit conversions but not accepting 1 as a double literal is needlessly nitpicky.

Overall, I'm super impressed with the progress on the language, editors and rustup cargo ecosystem. For an embedded project that didn't need to interface with too much existing code I would consider Rust. That said, Rust programs are certainly not "safe" in a broad sense, as they tend to bake in crashes. I also copied some code from Stack Overflow which sent non-nul terminated strings to an API which needed the nuls, and wasn't warned about it.


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK