4 ms·
I've been studying Rust on and off in the past 3 months. There are a few things that I find annoying, they are somehow discussed in the issue tracker, but it's
by giovannibajo1 8y ago
I've been studying Rust on and off in the past 3 months. There are a few things that I find annoying, they are somehow discussed in the issue tracker, but it's not clear to me what the priorities are to land them:
* Partial borrow. I can't borrow a field of a structure and then reference another field. I can't cite how many times in a few weeks I've been forced to rethink and split my structures just to work around this, making my code harder to read and write for no good reason than working around this limit of the borrow checker.
* The Rc/RefCell/Box dance is very verbose, when you just want to share a reference to some long-living object within other long-living objects. Worse than verbose, it also causes a runtime overhead because of the RefCell.
* No simple way to declare non-const global objects, like something as easy as a global HashMap of constants strings to constant integers. I'm a little spoiled from Go's excellent support of init functions (both compiler-generated and manually written), but this is something I really miss in Rust. lazy_static! is a workaround which is harder to use and forces the code to be thread-safe even if no threading support is necessary (that is, I can't use my non-thread-safe classes in a lazy_static).
* In generic code, there is no simple way to represent integer types. You need to use the "num" trait which breaks the "as" operator causing big pain and less readable code (as it's normal to covert between integer types).
* No integer generic parameters. We have to use the "typenum" crate which ends up causing less readable code and generates C++-like error messages.
* PhantomData is awkward, it absolutely sounds like something the compiler should handle, not the programmer.
* "Wrapping<u32> + 1" doesn't compile, but is there a good reason why it does not? I really wish Rust added wuNN/wiNN native wrapping types, or evolved in a way that I can write the same code with "u32" and "Wrapping<u32>" (another example: "as" doesn't work). Right now, it seems like the solution is half-baked: it tries to use the generic to avoid adding a native type, but the code that users must write is more bloated compared to native types.
- sanxiyn 8y agoPartial borrow works for me. Yes, if it didn't work it would be very annoying.
- CUViper 8y agoIt works when you access fields directly. Where I've seen people get frustrated is that calling a method on `&self` or `&mut self` is always a full borrow, even if it only really accesses and returns something partial. The borrow checker doesn't look to see what that method really uses in its implementation, just that the signature would allow full access.
- Filligree 8y agoThat's... understandable, perhaps. It would be difficult to implement something better, yes, but I'm also not sure it'd be better. In OO thinking, the question of which fields a method accesses should be an implementation detail, which shouldn't affect how the code calling it can look. If the borrow checker was smarter, then a change to a class implementation could break the code using it.
- CUViper 8y agoYeah, Rust usually eschews the sort of non-local reasoning that would require. It would be harder to implement, and harder for the programmer to think about too, especially with changes from afar as you say. Also, auto-`Deref` can make this even more surprising! If you have some `foo: Rc<Foo>`, then `&foo.bar` borrows the whole struct via `Rc::deref(&self)`. You can sometimes get around this by taking a full dereferenced borrow yourself -- `let foo: &Foo = &*foo;` -- and then work with partial borrows from that. Same goes for `DerefMut`, or really more so since `&mut` is exclusive.
- lmm 8y agoDid delegation ever make it into Rust? That would let you do a more comprehensible kind of partial borrowing: if you have impl TR for S { delegate * to self.f; } then calling TR methods should only require borrowing/owning f rather than the whole of self.
- oconnor663 8y agoThere aren't any new delegation features since 1.0 unfortunately. That said, if it worked this way, delegation would become part of a type's public API, such that changing the delegation details internally could break callers.
- steveklabnik 8y agoIt can be hard to get a hold on so many features, I hear you. Some of this is Rust, some of this is your perspective vs. Rust's perspective. Here's what I have for you: * Partial borrow. The status of this is unclear. It does work in many cases. It's complicated. I would push back a little on the "no good reason" though, but this is gonna be long so I'll leave it at that. * Rc/RefCell/Box Yes, this is painful, but that's because shared ownership is painful. Rust is pushing you away from a certain kind of architecture. Many people think this is a good thing... * No simple way to declare non-const global objects That is lazy_static. But > forces the code to be thread-safe even if no threading support is necessary (that is, I can't use my non-thread-safe classes in a lazy_static). This is perceived as a good thing; if you code changes later to use threads, you're not hosed. If you want something that's not for multi-threading, then don't use something that can be used with multiple threading! TLS is probably what you actually wanted here. * In generic code, there is no simple way to represent integer types. We went through three versions of these traits, and were not happy with any of them. It's a tough problem. * No integer generic parameters This is coming; the design work has been accepted, and now it's on to implementation. It's expected in nightly by the end of the year and probably stable early next year. * PhantomData is awkward, it absolutely sounds like something the compiler should handle, not the programmer. The compiler could try to infer variance, but that means that if your code changed, you'd have a breaking change. In general, around interface boundaries, Rust requires you to state what you want. It's even less awkward than what came before it, for what it's worth. * "Wrapping<u32> + 1" doesn't compile Rust is pickier about numbers than many langauges; 1u32 + 1u8 doesn't compile either. Many people find this valuable; you don't have to memorize complex promotion rules, and you always get the semantic you asked for.
- dikaiosune 8y agoRe: non-thread-safe items in lazy_static, you can also wrap them in a mutex within just the lazy_static so the struct only uses synchronization when it actually needs to be thread-safe.
- deleted 8y ago[deleted]
- stouset 8y agoAs someone who absolutely loves Rust, I think this is a really excellent critique of the language for a very specific reason: many of the issues listed here are the result of intentional tradeoffs or hard problems that are a consequence of the language's design. I personally think Rust makes the right call on many of these decisions, but it's very worthwhile to have a sober look at the cost. Rc/RefCell/Box are verbose because they deal with shared ownership, and Rust forces you to solve problems around shared ownership that other languages don't. This is usually a good thing, but in times where you do require this type of architecture, there's a verbosity cost. Arguably the real tradeoff here is a loss of brevity for a gain in correctness. Non-const global objects has definitely been a pain point for me. `lazy_static!` has always felt like a hack (although a community member released a possibly-better alternative just today[1]). I personally think HashMap literals might make much of the pain here go away. `Wrapping<u32> + u32` is a result of Rust avoiding doing implicit coercion, which has advantages but also comes at the cost you've enumerated here. [1]: https://www.reddit.com/r/rust/comments/9406rl/once_cell_a_lazy_static_without_macros_and_more/ https://www.reddit.com/r/rust/comments/9406rl/once_cell_a_la...
- childintime 8y ago> Rc/RefCell/Box are verbose because they deal with shared ownership, and Rust forces you to solve problems around shared ownership that other languages don't. Would there be a space for a mode inside Rust, call it a DSL if you will, where Rust behaves like those other languages? I mean that in this mode it would use the heap by default and copying to not alert the borrow-checker?
- steveklabnik 8y agoIt could work, but there hasn't been a ton of demand for it. Yes, it might help at the very beginning, but people do learn and become productive without it. It'd also be a worry if libraries started doing stuff like this; it'd be harder to use from "normal" Rust.
- floatboth 8y ago> Partial borrow Yeah, that's the annoying part of Rust :( > I can't use my non-thread-safe classes in a lazy_static Well, you can, if you wrap them in Fragile/Sticky from https://github.com/mitsuhiko/rust-fragile https://github.com/mitsuhiko/rust-fragile :) > PhantomData is awkward, it absolutely sounds like something the compiler should handle, not the programmer This is quite common in Haskell (called Proxy there), for different reasons (not for lifetimes since there are none — but for passing type-level stuff around calls), but because of that, it feels completely natural to me.
- eddyb 8y agoSince nobody mentioned it by name, the TLS replacement they're suggesting for lazy_static is `thread_local!` and it's part of libstd.