21 ms·
Things you can’t do in Rust (and what to do instead)
- deleted 6y ago[deleted]
- CraigJPerry 6y agoI’ve read quite a lot of the logrocket posts recently and the writing is always a satisfying read. Logrocket and fasterthanli.me are my two favourite resources for rust opinion stuff. Opinionated but fair rather than ideological. For my first brush with rust i decided i’d do some graph processing - a topic i’ve historically written buggy code for and i wondered if all the hype around rust’s compiler might help. Experienced rust people know where this is going but suffice to say i WOULD recommend this for someone new to rust if you want rapid exposure to what the borrow checker can and can’t do for you, for a rapid introduction to Rc & Arc, along with Box etc. I think i got a quicker introduction to rust by picking a hard use case.
- geewee 6y agoLogrocket finds writers on other sites like Medium and approaches them and pays them reasonable rates (2-500$ per post iirc) to write for them. Source: I have written for them in the past.
- CraigJPerry 6y agoAha! That’s quite a neat approach, i wouldnt know what logrocket does otherwise but i feel pretty aware of their product these days.
- thejosh 6y agoIt's a great way to get decent content, I think DigitalOcean and Linode use (still?) do that. Great marketing tactics to boost website traffic :-).
- gspr 6y agoI understand the message they're trying to convey, but a better title would perhaps be "things you can't do without unsafe Rust". There's nothing inherently bad or wrong about unsafe Rust. It just leaves it up to you to manage invariants like lifetimes and aliasing contraints – just like in C.
- llogiq 6y agoWell, titles are hard. All of those things are possible in Rust. But they're not easy or approachable.
- qznc 6y agoWhat I appreciate coming from D is fine grained control of mutability. In Rust it is all or nothing. You cannot have a mutable container of immutable items. In D a string as a mutable array of immutable characters, for example. You can append things but not change the existing parts.
- masklinn 6y ago> In D a string as a mutable array of immutable characters, for example. You can append things but not change the existing parts. Nothing precludes designing a Rust string that way. You're still designing the mutation interface of the collection, and having a `&mut` to a structure doesn't really give you anything if the structure doesn't allow mutations. Hell, you could actually design your own pseudo-string, which derefs to an immutable string, and only provides for a select number of mutations. But, given Rust's rules around mutation, what useful property would that yield exactly? I think that's one of the most interesting effects of Rust's strict ownership, very much an "if a tree falls" situation: if you mutate a string in place but there's no way for anyone else to observe that mutation, does it matter? I can imagine that "append-only strings" would be useful in a world of multiple-ownership where you could delay COW copies without affecting co-owners (though even that seems… somewhat limited), but that's not really a thing in Rust. Given the cow-ownership would be not one but two layers of external wrappers (an Rc/Arc pointer, and some sort of internal mutability shim), it's not like you could not flatten the entire thing and build thread-safe append-only COW pseudo-strings.
- pornel 6y agoRust can have mixed mutability - see Interior Mutability (Cell/RefCell/Atomic/Mutex). But in Rust the distinction is actually about shared vs exclusive access. This is the aspect that really matters for safety. Access is properly of the code, not data, so it can be dynamic from data's perspective (the same data can be borrowed exclusively or not at different times) When you have exclusive access, immutability becomes irrelevant, because nobody else can observe the mutation (so e.g. exclusive reference to a Mutex can skip locking!)
- rhodysurf 6y agoI like rust a lot, but coming from C++ and python there are times inheritance saves so much time enabling quickly creating small variations in classes. In rust you have to duplicate all the base logic, or abstract it to another common type to use composition. Which is fine but it’s painful at times knowing that time sink lies ahead. I understand everyone hates OO now but I still miss it at times when it would save me a lot of effort.
- inglor_cz 6y agoIn my programming career, I had several situations where a base class made a lot of sense, with a dozen derived classes only implementing specific functionality. They were mostly algorithm-related. OO makes a lot of sense in hierarchic structures.
- Kinrany 6y agoIt's a good practice to make the base class abstract. With that in mind, this pattern cleanly maps onto a trait with default implementations for methods plus several structs that implement the trait.
- domenukk 6y agoTo be fair, a trait gets you 98% there. It can have default implementations for most functions, you just overwrite the ones you want to change. You'll still need to declare the underlying struct though, so a bit of extra code
- edflsafoiewq 6y agoUnconstrained templates can also sometimes be a massive time saver.
- kubb 6y agoCan you imagine a non-inheritance solution to this problem? If you used this way of solving things your whole life then I understand that changing paradigms is a challenge.
- latch 6y agoI've started to learn rust for the 2nd time. The first time was a really frustrating. In my defense though, I'm pretty sure the compiler inferred a lot less back then (maybe 5-6 years ago now?) This time, it's going much better and I've managed to build something non-trivial. I quickly ran into the self-referencing issue. A simple structure to hold an [normalized] phrase and a list of words within that phrase: struct Input<'a>{ words: Vec<&'a str>, phrase: String, } I ended up asking in https://users.rust-lang.org/ https://users.rust-lang.org/ where people are super fast, friendly and helpful. I understand that it isn't allowed, but I still don't quite understand why it isn't. It seems completely straightforward and unambiguous to me. Something else I'm surprised the article didn't mention is the combination of `Box<dyn X + Sync + Send>` which is a mouthful - both in terms of having to declare a type with this, and in terms of everything is implies/encapsulates.
- danhor 6y ago(Not sure if this is the official explanation, but makes sense to me) The issue is that you have references to the String (it's borrowed). That means it can't change, which isn't/can't be expressed in this struct. Imagine clearing the String: Suddenly all references in words are invalid which means you're referencing something invalid, very much not memory safe and a big rust no-go
- thejosh 6y agoYeah, I tried picking rust up a few years ago and ran into a few weird issues that would block me from making good progress. I've now used it to create a parser that reads data and converts it to other formats (for my bespoke needs) and it's so fast it's crazy. I had much less issues this time, CLion integration with Rust is really helpful as well. The tooling around rust is now great (rustup, cargo, etc).
- zxzax 6y agoI just tried that in nightly and it seems to work? Not sure what the issue with that is.
- ptato 6y ago
- pron 6y agoI'm watching with both amazement and amusement how Rust is undergoing the exact same process we went through with C++ in the late '80s and early '90s, pushing the belief that the same language should be used for both low-level and high-level programming. Back then, just as it is now, that idea was sold by programmers who are on the more capable end of the spectrum and who also enjoy spending a lot of time thinking about clever ways to express programs in a rich programming language. Then, as now, they were people who hadn't had much contact with the average software developer and with the realities of software mass-production. The few of them who had, also tried convincing the industry that the system should change, and that software should be developed by small elite groups who are masters of the discipline as well as discipline (I'm not talking about the people who like Rust, or C++, for that matter, for low-level programming, but about those who try to sell low-level programming for application development), but, of course, the economics always win. Those who go down that path will experience the same disillusionment we did, and end up with applications that end up costing so much more to maintain if only because of the cost of the maintainers they require. Software costs work better when low-level and high-level programming are kept separate. The former is costlier but tends to be both smaller in size and in prevalence, while the latter is the opposite. Of course, the number of people who make that mistake of mixing high- and low-level programming won't be nearly as high now as it was back then -- many of us have learned our lesson -- but it would still be interesting to watch this unfold again, even at a small scale. And, thirty years from now, some will be telling us why, while using Rust for high-level programming was indeed, a costly mistake, Muju is completely different, and this time a low-level language really and truly is appropriate for application programming.
- layoutIfNeeded 6y agoEvery generation must go through these cycles. Visual programming, no-code solutions, thin clients, thick clients, static typing, duck typing, functional programming, etc. Time is a flat circle.
- dathinab 6y ago> more capable end of the spectrum Actually rust is a grate language for programmers of the less capable end of the spectrum. While it allows clever abstractions it generally discouraged and recommended to write simple code. The borrow checker might seem like a clever abstraction, but it's just a build in code analytics tool you should use anyway when using C or C++ or well any language, and it generally nudges you to better structured code. > Then, as now, they were people who hadn't had much contact with the average software developer and with the realities of software mass-production. Except that more than just a few decisions in the rust design process where strongly influenced by the feedback of average developers, including people which just got started with programming. Wrt. C++ I can completely agree, it's a completely over engineered language with endless gotchas with silently sneak into you code without you noticing and potentially causing UB if compiled with some compiler in some optimization levels for some target. Making such bugs completely bonkers to track down. On the other hand in (safe) rust many of the problems user have come from the language helping you to not run into any of this and other bugs. And all "hidden" complexity rust has is normally designed in a way so that you don't need to know it to use rust or even things related to it as long as you don't want to create some low level primitives which involve unsafe code.
- yannoninator 6y agoI have to give it to logrocket's articles, using Rust for a marketing push and being on the frontpage of HN. Seems like I should make a Rust article just for it to soar to HN's frontpage, just for developer marketing of my product that is unrelated to Rust. Bravo.
- llogiq 6y agoAuthor here. I blog about Rust as a hobby (and have been doing so since 2015 on my personal blog), and LogRocket is so nice to pay me to have my content hosted on their blog. And yes, their product is only tangentially related (if you use Rust on the frontend with WASM).
- smoldesu 6y agoIt's probably a little jarring to see this many Rust articles to most people, but it's a very exciting time in the Rust community. Not only has the Rust Foundation recently been established, but the crate ecosystem is now starting to finally include higher-level software like game engines and SDKs. Rust has actually entered production, a mere 7 years after it was conceptualized and prototyped. Now that it's starting to mature, Rust is starting to look like a great choice for a high-performance native development stack.
- PragmaticPulp 6y agoI worked at a company that had a developer blog with articles like this. It was fun, but our conversion rate from the blog was zero. Maybe there was some downstream brand recognition effects, but we treated it like a fun thing for developers to do to share some knowledge. The only real downside was that a couple people got hooked on the idea of using the blog for thinly veiled self-promotion, so it required some clear expectation setting. It worked best when posts were treated as group efforts by the team instead of individuals posting to the company blog with everyone else as silent editors.
- dang 6y agoLooks like this is only the second one: https://hn.algolia.com/?dateRange=all&page=0&prefix=true&query=comments%3E0%20logrocket.com%20rust&sort=byDate&type=story https://hn.algolia.com/?dateRange=all&page=0&prefix=true&que.... That's not excessive. Rust posts have gotten excessive of course, but that's different.
- volta83 6y ago> Rust only ever allows one owner per data, so this will at least require a Rc or Arc to work. But even this becomes cumbersome quickly, not to mention the overhead from reference counts. This takes 50 LOC, gives you a doubly-linked list that's memory safe, thread safe, and that has pretty much the same efficiency as a doubly-linked list using raw pointers. (Feel free to prove me wrong here, but I've written one, and I couldn't measure the difference on x86, i'd expect ARM to be even better because it has weaker atomics). Compared with a Vec<T> or a HashMap<T>, what dominates the performance of a doubly-linked list is the pointer indirection to access the object, and the cost of that is pretty much the same whether you are using Arc<T>/Rc<T> or a raw pointer. Also, a doubly linked list only makes much sense for relatively large objects and when you want O(1) splice, so whether you store 64-bit or 128-bit wide pointers doesn't matter at all, because the objects are big, and O(1) splice just modifies 4 pointers...
- imtringued 6y agoYeah, the article is weird about "But even this becomes cumbersome quickly, not to mention the overhead from reference counts." You pick the linked list because of better scalability as the data size grows bigger, you choose them precisely because you think the constant overhead vs contiguous lists is worth it. The most common example is an object that wants to remove itself from a big list. E.g. you have a OS with 10000 processes. 1000 processes die per second and they already know their position in the linked list so they remove themselves in O(1). An array would require leaving the slot empty and swapping in the last element (unordered list) O(1) or shifting entries to fill the gap (ordered list) O(n). The former breaks ordering, the latter leads to O(nm) removal costs where n is the number of live processes and m the number of dying processes. Paying reference counting costs is not a problem.
- IshKebab 6y agoA linked list is still a pretty terrible choice. You'd probably be better off with a Vec with tombstones and some kind of free list.
- 6y ago
- benreesman 6y agoRant disclaimer. Proceed at your own risk. I want to like Rust, if only because anything has to be better than C++. C++ is godawful and it sucks that all these decades later it’s what serious big software is written in. But I haven’t found the explanation of Rust and it’s trade offs and philosophy written by someone who can write serious Haskell and Lisp and C++ and is like “Rust. This is the way. Here’s how and why.” I’m sure there are much better blog posts than this one that shed light on how parametric as opposed to ad-hoc polymorphism is natural in Rust with no runtime cost. Or how Rust’s nested angle-bracket hell to get a pointer to a piece of memory is actually a deep and profound algebra that exposes std::move for the fraud it actually is or whatever. Where do I find the explanation of why Rust is less awful than C++ written by someone who has written a lot of C++ out of necessity, doesn’t take non-browser JS seriously, doesn’t think 8 boxes need kube, doesn’t stand to make consulting revenue off having been involved in Cargo, and generally uses C++/Python because they’ve got work to do? Where do I go to see the light as someone who has some idea how brilliant Eich is and also knows how ridiculous it is to use JavaScript when you’ve got an alternative better than Lua? (I’ve written a JavaScript compiler, I know how unfortunate node.js and Electron are). I want to be sold! Sell me!
- Glavnokoman 6y agoSounds like you might like Zig. It is not perfect either, but many things are done the proper way there. The only thing I really like about Rust it is that it tries to solve many problems at their roots. It failed in my opinion, but at least it tried to.
- benreesman 6y agoWhat’s the link that takes me down the Zig rabbit hole?
- matheusmoreira 6y agoThe Road to Zig presentation: https://youtu.be/Gv2I7qTux7g https://youtu.be/Gv2I7qTux7g C but with the problems fixed.
- hawski 6y agoI still couldn't get or understand an answer to this question: https://stackoverflow.com/questions/64705654/why-i-get-temporary-value-dropped-while-borrowed-if-i-assign-but-not-when-pas https://stackoverflow.com/questions/64705654/why-i-get-tempo... Why I can put a temporary inside a single expression, but if I bind it first just to move it inside I'm not allowed? This works: use lol_html::{element, HtmlRewriter, Settings}; let mut output = vec![]; { let mut rewriter = HtmlRewriter::try_new( Settings { element_content_handlers: vec![ // Rewrite insecure hyperlinks element!("a[href]", |el| { let href = el .get_attribute("href") .unwrap() .replace("http:", "https:"); el.set_attribute("href", &href).unwrap(); Ok(()) }) ], ..Settings::default() }, |c: &[u8]| output.extend_from_slice(c) ).unwrap(); rewriter.write(b"<div><a href=").unwrap(); rewriter.write(b"http://example.com>").unwrap(); rewriter.write(b"</a></div>").unwrap(); rewriter.end().unwrap(); } assert_eq!( String::from_utf8(output).unwrap(), r#"<div><a href="https://example.com"></a></div>"# ); With the temporary vec moved outside it errors with "temporary value dropped while borrowed" on the "let handlers" line. use lol_html::{element, HtmlRewriter, Settings}; let mut output = vec![]; { let handlers = vec![ // Rewrite insecure hyperlinks element!("a[href]", |el| { let href = el .get_attribute("href") .unwrap() .replace("http:", "https:"); el.set_attribute("href", &href).unwrap(); Ok(()) }) // this element is deemed temporary ]; let mut rewriter = HtmlRewriter::try_new( Settings { element_content_handlers: handlers, ..Settings::default() }, |c: &[u8]| output.extend_from_slice(c) ).unwrap(); rewriter.write(b"<div><a href=").unwrap(); rewriter.write(b"http://example.com>").unwrap(); rewriter.write(b"</a></div>").unwrap(); rewriter.end().unwrap(); } assert_eq!( String::from_utf8(output).unwrap(), r#"<div><a href="https://example.com"></a></div>"# );
- 6y ago
- da39a3ee 6y ago> You could surely argue that even in the OO world, inheritance has a bad reputation and practitioners usually favor composition if they can. You could indeed argue that, if you’d never been part of a community writing code in an object oriented language and your opinions about actual OOP practice were formed by reading HN.
- runawaybottle 6y agoThis language just does not look like much fun at all.
- llogiq 6y agoLooks can be deceiving. You'll have to try it to see if you like it. And if not, that's totally OK.
- Traster 6y agoShouldn't the java in the first example explicitly state it implements the interface?
- llogiq 6y agoOh, right! Thank you for bringing it up, I'll fix this.
- stared 6y agoThere are some examples which (IMHO) are not the best. First, global variables changed silently is a tempting yet very bad idea for software engineering. Yes, we are used to them, but there are unsafe. You can easily create a mutable variable in the main, then explicitly change it with a function. So, no features are lost, but it enforces clarity and explicity. Second, I don't find initialization with zeros to be a huge problem. I got surprised it got mentioned at all.
- ljm 6y agoWhile I was interested in the article, the mistakes in the code examples are so frequent that, by the end, I was wondering if the array example had an error. I couldn't really make sense of the suggestion there. These issues would easily be captured in a first draft or with a simple review of the examples.
- EamonnMR 6y agoI wish the author had gone a bit more in depth on the doubly linked lists and recursive data structures. Those are enormous pain points when trying to do conventional low level programming in Rust, and doing it right the first time might help avoid the pitfall of 'build 90% of what you want, realize you need to redo everything because there's an ownership issue you need to hack around, give up on Rust.'
- nestorD 6y ago`Learn Rust With Entirely Too Many Linked Lists`[0] is a great tutorial on the subject. [0]: https://rust-unofficial.github.io/too-many-lists/ https://rust-unofficial.github.io/too-many-lists/
- pizza234 6y agoI'm somewhat skeptical of the article - I think it's not entirely accurate. This is not a good example for a global counter: static mut DATA_RACE_COUNTER: u32 = 1; fn main() { print!("{}", DATA_RACE_COUNTER); // I solemny swear that I'm up to no good, and also single threaded. unsafe { DATA_RACE_COUNTER = 2; } print!("{}", DATA_RACE_COUNTER); } because there are safe APIs for this: static DATA_RACE_COUNTER: AtomicU32 = AtomicU32::new(1); fn main() { print!("{:?}", DATA_RACE_COUNTER); DATA_RACE_COUNTER.store(2, Ordering::Relaxed); print!("{:?}", DATA_RACE_COUNTER); } Two examples are actually presented about global mutable state, and they're both unfit. Also this: [the linked list] will at least require a Rc or Arc to work. But even this becomes cumbersome quickly, not to mention the overhead from reference counts. AFAIK accessing an object via RC has no overhead per se (RCs introduce overhead when they're manipulated). Even if it had, it should be verified that the overhead has a measurable performance impact (which is something that can't be generalized). The point about array initialization is fair - custom initialization requires unsafe code (or a crate) - but it must be considered that it's a performance optimization.
- llogiq 6y agoNot all platforms have atomics; you'll mostly encounter this on embedded systems. That said, use them if you can! Regarding Rc, the overhead is more in memory than time, which usually has a non-linear relation to performance, so any overhead is really hard to guess or estimate.
- aazaa 6y ago> Arguably the most-asked-about missing feature coming from object-oriented languages is inheritance. Why wouldn’t Rust let a struct inherit from another? This concern faded away as I used Rust a lot. It turns out that inheritance of implementation isn't nearly as useful as it's cracked up to be. And it carries a lot of baggage. The example hints at this, but unfortunately without driving the point home. Rust's enums (used to illustrate static dispatch) are amazing, and one of the best (of many) language features. Enums can contain data of their own (as enum structs or enum tuples), opening up many more possibilities than the example might imply. On two large projects, I've had no need for inherit-the-implementation style OO. And it's just fine.
- ebingdom 5y ago> Rust's enums (used to illustrate static dispatch) are amazing, and one of the best (of many) language features. Enums can contain data of their own (as enum structs or enum tuples), opening up many more possibilities than the example might imply. Algebraic data types are table stakes for any programming language in my book. I don't know why it's taken OOP programmers so long to come around to them (and most still haven't). It bothers me that Rust calls them "enums", even though "enum" already means something different to most programmers and we already have several terms that describe the feature that Rust has (coproducts, variants, sum types, discriminated unions, tagged unions, and, more generally, algebraic data types). This makes talking about programming languages more difficult than it needs to be, since when someone says "enums" I now have to ask what programming language they are referring to, since "enum" now means different things in different languages.
- staticassertion 5y agoI think because if you're building with a language that's extremely interface oriented you probably don't want to think "if it's this type, do x, if it's that type do y" manually. Instead that would be another job for another interface. But I think that's more a symptom of having a powerful hammer than actually designing well, and sum types solve a different problem than interfaces.
- Gaelan 5y ago
- deleted 5y ago[deleted]
- ibraheemdev 5y ago> Finally, it’s possible to implement a trait for all classes that implement one of a number of other traits, but it requires specialization, which is a nightly feature for now (though there is a workaround available, even packed in a macro crate if you don’t want to write out all the boilerplate required). This is false. You can easily write a blanket impl in stable Rust: impl<T> Foo for T where T: Bar + Baz { fn foo(&self) { self.bar().baz() } } Specialization allows you to create multiple impls, depending on what traits a generic type implements. This is a very powerful feature that is available on nightly, but it is also easy to misuse and create unwanted implicit behavior. impl<T> Foo for T { default fn foo(&self) { // do nothing by default } } impl<T> Foo for T where T: Bar + Baz { fn foo(&self) { self.bar().baz() } }
- Diggsey 5y agoThe author is referring to the case where you have multiple traits, and want to provide implentations like: impl<T: Cat> Animal for T { ... } impl<T: Dog> Animal for T { ... } This is impossible without specialization, since there's no way to express the constraint that no type implements both `Dog` and `Cat`. Having multiple blanket trait implementations like this can be used to emulated inheritance, whereas a single blanket implementation cannot.