It may come as a surprise to some but most systems engineering has very little hard computer science involved and is mostly about achieving very simple tasks in reliable ways with very robust error handling.
You rarely implement novel data-structures or algorithms. Not shipping with cyclic data-structures in Rust is not really a problem for most systems applications. That said, you actually -can- build them in Rust, and it's likely there will be nice libraries with various different allocation/memory management and other runtime tunables and performance tradeoffs.
If you exclusively consider hard computer science problems as "hard problems" then no, Rust is probably not for you. Consider Haskell or Julia.
> What are the technical reasons you can't guarantee an absence of leaks?
Well, 'leak' is one of those things that's easy for a programmer to understand, but a hard thing for a computer to understand, because it's really about intent. How long did you intend for some resource to live? Any global value is, in some sense, a leak. We had a long discussion about this, and, at least currently, we couldn't come up with a formal enough definition of 'leak' to even start tackling the problem of "how do we solve leaks." (It is entirely possible that I am unaware about research on this topic... but given that solving leaks wasn't a goal of Rust, fixing it would just be gravy anyway. You have to choose your battles, and Rust certainly isn't perfect.)As for how safe Rust can leak:
let x = Box::new(5);
std::mem::forget(x);
which can itself just be implemented in safe code: use std::cell::RefCell;
use std::rc::Rc;
fn forget<T>(val: T) {
struct Foo<T>(T, RefCell<Option<Rc<Foo<T>>>>);
let x = Rc::new(Foo(val, RefCell::new(None)));
*x.1.borrow_mut() = Some(x.clone());
}
or something like "I have a thread that is holding the receiving end of a channel that infinitely loops without reading anything off," in which case anything sent down that channel leaks.It is technically possible to guarantee the absence of leaks by introducing a `?Leak` trait to the language. There was a point in time when many wanted Rust to go that way, but it was ultimately decided against (it complicates other things).
this is probably a silly question because the content, though it might always be in RAM, will be branched to an order-of-magnitude less than it'll be used (once it's been loaded to cache)...but still, there should be an observable temporal-boundary to the context stored in cache and branch predictor in a procedural language, that would be manifest as an abstraction-boundary in a more parallelized environment
And frankly: Your definition of "hard" is a bit restrictive.
not a fan of C++ but I personally think having to wade through the building blocks of C was an immensely valuable reverse turing test (ability of human to exhibit human-like behavior? apparently this term is already taken but you get my idea) and I bemoan the future where everyone is protected from their mistakes. How else did one build motivation to do it right? The gamble of a mistake is much more fun than of a compiler error.
Like a fertilizer that stimulates growth while young but a crutch which stunts advancement in old age.
??? These would be super easy to build on top of Send/Sync and lifetime tracking.
You might be confusing Rust the language with a hypothetical Rust/OTP or Rust/HOL or something.
Without those, we're going to see a hundred different solutions crop up, none of them being interoperable.
I'm also sort of hopeful that higher-kinded types can do something about writing code that can run on any such system. If you can have some functions on an abstract Promise<T> type, like fn then(Promise<T>, fn(T) -> Promise<U>) -> Promise<U>, it doesn't super matter which backend you're using. (As I understand it, JS is already here, because they can duck-type their promise spec.)
https://crates.io/search?q=graph
> No support for trying
do I need to have functional experience for exposure to that? Only thing you listed that I haven't heard of.
I would not be shocked if someone figured out a brilliant way to add dependent types to Rust 1.x within the next few years.
You might want to check that the property "the number of black nodes from the root to any leaf is the same" is always valid. With unit testing, you'd implement the insertion and deletion functions, and write a unit test where you give them a few trees and make sure that the insertion and deletion functions don't break that property, probably by looping over every leaf and comparing the black-height. If this passes, you know that your functions are probably correct and at least not obviously broken, but you don't know that there isn't some edge case you haven't thought of.
With a language that supports proofs, you'd tell the compiler about the concept of black-height, and the type of a red-black tree would include the black-height. Therefore, in order to construct a valid red-black tree, you have to have constant black-height. Just as much as you couldn't construct a red node with a red child, you can't construct a node with two children of varying black-height. When you say that your insertion function returns a red-black tree, as part of type-checking, the compiler makes sure that these properties hold -- because in turn, every helper function you call, every constructor, etc. also requires these properties to hold so that they can return an object of type red-black tree.
The downside is that it's much more involved than a few randomly-selected test cases to convince the compiler that you're upholding the red-black tree rules in every single case. This textbook has an example of doing this in the Coq proof assistant (search for the section labeled "Dependently Typed Red-Black Trees"):