Realistically, other than cool factor I'm having a real hard justifying the use of rust. It either devolves into using reference counting (fine but we have lots of languages that do that without borrow checking), or dynamics (i.e refcell).
The type system is nice with the algebraic data types, but lacking the higher orderedness of languages like Haskell, it just seems sad.
So basically, to me it seems like you're getting the worst of both worlds. You don't get the freedom of c/c++ and you don't get the actual type safety of a language like Haskell (refcell kills the notion of compile time checking).
Realistically what needs to happen is some system for circular references like Haskell has (let bindings).
There's so much you can do in rust without messing with any of that, and the only time you have to get deep in the weeds arguing with the compiler is _exactly_ the sort of code that you _should_ be spending a lot of time carefully thinking about. That C allows you to write that code without thinking about it much is _not_ a benefit to using C.
Data structures with circular references are so incredibly rare in practice. I've occasionally needed to use a graph (sometimes even a cyclic one), and petgraph with integer indexes works fine for that. Other than that, I've been writing Rust full-time for over 7 years and really not needed them.
The benefit is that for almost all code you get mutability xor aliasing, which I think is incredibly important to achieving correctness at scale.
Doubly-linked lists are incredibly rare, few people should ever use one and even fewer people should ever write one. In almost all cases vectors are better [1].
[1] There are some cases where they're useful, e.g. when amortized O(1) append is not enough and you want actual O(1) append. But this matters to almost no one.
In almost all cases, it is better to use a vector for both those data structures. Cache locality is king, memcpy is blazing fast, and pointer chasing is generally pretty bad for performance.
For a stack, just use a regular vector. Pushes are amortized O(1), and pops are O(1).
For a queue, a ring buffer backed by a vector. (This is how VecDeque in Rust's stdlib works.) Again, enqueue is amortized O(1) and dequeue is O(1).
Even if you do need to use a linked list, it's generally better to depend on someone else's implementation than write your own.
In that case, you'll find that Rust is an excellent language for writing doubly-linked lists.
A lot of the point of Rust is to make a few people do the hard work of writing some complex data structure in unsafe code, and let everyone else reap the benefits.
> It either devolves into using reference counting (fine but we have lots of languages that do that without borrow checking), or dynamics (i.e refcell).
The point is that you don't have to do that for all of your data objects, unlike other languages that use refcounts and dynamic checks under the hood. Most alloc/dealloc in Rust can be dealt with via simple RAII.
I don't see what let bindings have to do with solving the circular reference problem, those are just local bindings. A Haskell doubly-linked list would use IORef, but no one would ever actually do that.
data DLList a = DLList a (DLList a) (DLList a) | End
make_dllist :: [a] -> DLList a
make_dllist = go (\_ -> End)
where go mkPrev [] = mkPrev End
go mkPrev (x:xs) = go (\next -> let me = DLList x (mkPrev me) next in me) xs
No need for IORef. Too many people misunderstand the meaning of 'let' in Haskell. It doesn't mean what you think it does. It defines, not assigns. In rust, let 'declares' and assigns in one go. It does not define.