3 ms·
One day I will write a blog post called “The Rust borrow checker is overrated, kinda”. The borrow checker is certainly Rust’s claim to fame. And a critical rea
by forrestthewoods 1y ago
One day I will write a blog post called “The Rust borrow checker is overrated, kinda”.
The borrow checker is certainly Rust’s claim to fame. And a critical reason why the language got popular and grew. But it’s probably not in my Top 10 favorite things about using Rust. And if Rust as it exists today existed without the borrow checker it’d be a great programming experience. Arguably even better than with the borrow checker.
Rust’s ergonomics, standardized cargo build system, crates.io ecosystem, and community community to good API design are probably my favorite things about Rust.
The borrow checker is usually fine. But does require a staunch commitment to RAII which is not fine. Rust is absolute garbage at arenas. No bumpalo doesn’t count. So Rust w/ borrow checker is not strictly better than C. A Rust without a borrow checker would probably be strictly better than C and almost C++. Rust generics are mostly good, and C++ templates are mostly bad, but I do badly wish at times that Rust just had some damn template notation.
- lblume 1y ago> No bumpalo doesn't count. Mind explaining why? I have made good experiences with bumpalo.
- forrestthewoods 1y agoEverytime I try to use bumpalo I get frustrated, give up, and fallback to RAII allocation bullshit. My last attempt is I had a text file with a custom DSL. Pretend it’s JSON. I was parsing this into a collection of nodes. I wanted to dump the file into an arena. And then have all the nodes have &str living in and tied to the arena. I wanted zero unnecessary copies. This is trivially safe code. I’m sure it’s possible. But it required an ungodly amount of ugly lifetime 'a lifetime markers and I eventually hit a wall where I simply could not get it to compile. It’s been awhile so I forget the details. I love Rust. But you really really have to embrace the RAII or your life is hell.
- marcianx 1y agoTo make sure I understand correctly: did you want to read a `String` and have lots of references to slices within the same string without having to deal with lifetimes? If so, would another variant of `Rc<str>` which supports substrings that also update the same reference count have worked for you? Looking through crates.io, I see multiple libraries that seem to offer this functionality: [1]: https://crates.io/crates/arcstr https://crates.io/crates/arcstr [2]: https://crates.io/crates/imstr https://crates.io/crates/imstr
- forrestthewoods 1y agoIt’s deeper than that. Let’s pretend I was in C. I would allocate one big flat segment of memory. I’d read the “JSON” text file into this block. Then I’d build an AST of nodes. Each node would be appended into the arena. Object nodes would container a list of pointers to child nodes. Once I built the AST of nested nodes of varying type I would treat it as constant. I’d use it for a few purposes. And then at some point I would free the chunk of memory in one go. In C this is trivial. No string copies. No duplicated data. Just a bunch of dirty unsafe pointers. Writing this “safely” is very easy. In Rust this is… maybe possible. But brutally difficult. I’m pretty good at Rust. I gave up. I don’t recall what exact what wall I hit. I’m not saying it can’t be done. But I am saying it’s really hard and really gross. It’s radically easier to allocate lots of little Strings and Vecs and Box each nested value. And then free them all one-by-one.
- lokeg 1y agoSomething like the following? I am trying and failing to reproduce the issue, even with mutable AST nodes. use bumpalo::Bump; use std::io::Read; fn main() { let mut arena = Bump::new(); loop { read_and_process_lines(&mut arena); arena.reset(); } } #[derive(Debug)] enum AstNode<'a> { Leaf(&'a str), Branch { line: &'a str, meta: usize, cons: &'a mut AstNode<'a> }, } fn read_and_process_lines(arena: &Bump) { let cap = 40; let buf: &mut [u8] = arena.alloc_slice_fill_default(cap); let l = std::io::stdin().lock().read(buf).expect("reading stdin"); let content: &str = str::from_utf8(&buf[..l]).unwrap(); dbg!(content); let mut lines = content.lines(); let mut latest: &mut AstNode<'_> = arena.alloc(AstNode::Leaf(lines.next().unwrap())); for line in lines { latest = arena.alloc(AstNode::Branch{line, meta:0, cons: latest}); } println!("{latest:?}"); }
- bobajeff 1y agoThis is something I've been thinking about lately. I do think memory safety is an important trait that rust has over c and other languages with manual memory management. However, I think Rust also has other attractive features that those older languages don't have: * a very nice package manager * Libraries written in it tend to be more modular and composable. * You can more confidently compile projects without worrying too much about system differences or dependencies. I think this is because: * It came out during the Internet era. * It's partially to do with how cargo by default encourages more use of existing libraries rather than reinventing the wheel or using custom/vendored forks of them. * It doesn't have dynamic linking unless you use FFI. So rust can still run into issues here but only when depending on non-rust libraries.
- forrestthewoods 1y agoAgree on all points