[HN Gopher] Things you can't do in Rust (and what to do instead)
___________________________________________________________________
 
Things you can't do in Rust (and what to do instead)
 
Author : weinzierl
Score  : 263 points
Date   : 2021-05-15 10:27 UTC (12 hours ago)
 
web link (blog.logrocket.com)
w3m dump (blog.logrocket.com)
 
| stared wrote:
| There 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.
 
| volta83 wrote:
| > 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 or a HashMap, 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/Rc 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...
 
  | e12e wrote:
  | I appreciate this comment, as there was (semi) recently som
  | comments/posts about how linked lists in rust is hard - but
  | this makes it sound more like "a linked list in rust looks a
  | bit different (and is safer) than a linked list in c" (or:
  | "rust is actually its own language, work with the grain").
 
  | imtringued wrote:
  | Yeah, 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 wrote:
    | A 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.
 
      | titzer wrote:
      | It really depends on what you are doing. If inserts into
      | the middle are common, readjusting a vector gets really
      | expensive.
 
        | rmdashrfstar wrote:
        | Theoretically expensive, but modern hardware is optimized
        | for this case. Random access lookups and cache misses
        | from the linked list will tank performance, even in most
        | cases where a linked list is theoretically equivalent or
        | preferred.
 
        | titzer wrote:
        | If you have a long vector, say, 1000 elements, and you
        | need to copy 500 of them to shift them over for an
        | insert, you are going to be very slow compared to a
        | linked list. Besides, linked lists aren't that bad if the
        | nodes are close enough in memory (read: consecutive), or
        | they are hot enough to be in already in cache. But as
        | always, measure instead of guessing.
 
        | masklinn wrote:
        | > If you have a long vector, say, 1000 elements, and you
        | need to copy 500 of them to shift them over for an
        | insert, you are going to be very slow compared to a
        | linked list.
        | 
        | FWIW the original suggestion mentioned tombstoning so
        | you'd have a Vec> instead of a `Vec`, and
        | "removing" an item would just set the `Option` to `None`.
        | 
        | Of course this would increase iteration cost (as you'd
        | have to skip the `None`), and you'd probably want to
        | compact the vec once in a while.
 
        | kllrnohj wrote:
        | > If you have a long vector, say, 1000 elements, and you
        | need to copy 500 of them to shift them over for an
        | insert, you are going to be very slow compared to a
        | linked list.
        | 
        | It takes almost no time at all to do that, depending on
        | the size of the elements. But you have a starting
        | assumption there that the vector _must_ always be stored
        | sorted. Challenge that assumption. If insertion is
        | perfectly balanced with traversal it may be true, but
        | that 's also rarely the case. It's fairly trivial to
        | instead track if the vector is already sorted or not &
        | sort when sorted access is actually needed.
        | 
        | > Besides, linked lists aren't that bad if the nodes are
        | close enough in memory (read: consecutive)
        | 
        | Which doesn't really happen outside of a controlled
        | benchmark. If the linked list is stored in a single
        | consecutive allocation, then resizing has observable side
        | effects (eg, pointers to elements become invalidated).
        | You can do things like allocate chunks at a time so that
        | _some_ elements are consecutive, but you won 't have
        | _all_ elements consecutive or close in memory.
 
        | titzer wrote:
        | >> you need to copy 500 of them
        | 
        | > It takes almost no time at all to do that,
        | 
        | Uh, 500 loads _and_ dependent stores, at minimum, in the
        | best case hitting L1 cache, which is 3 cycles. So you are
        | talking about 1500 cycles to....avoid a single cache line
        | miss due to chasing a linked list pointer? A miss to main
        | memory is 100-200 cycles, maximum. More likely you are
        | going to L2 /L1 which is 12-50 cycles. So no, in no
        | circumstances would I expect copying 500 elements to be
        | faster than chasing a single pointer.
        | 
        | > Challenge that assumption.
        | 
        | You don't get to pick the application behavior. If it
        | needs to a do a lot of inserts and traversals, it just
        | does.
        | 
        | >> are close enough in memory (read: consecutive)
        | 
        | > Which doesn't really happen outside of a controlled
        | benchmark.
        | 
        | There is no supporting information for this statement at
        | all. Of course a list created all at once using a bump-
        | pointer allocator is going to be consecutive. And a
        | (moving) garbage collector is going to dynamically
        | reorganize the nodes of a list, generally in a breadth-
        | first way, depending on the marking algorithm. That can
        | result in the nodes of the list being laid out
        | consecutively after compacting, no matter where they were
        | originally organized.
        | 
        | Memory behavior of programs is complicated. I find it
        | surprising that we're having a conversation that we can't
        | or shouldn't use linked lists now because vectors are
        | universally better, or we can lazy sort or some other
        | crazy workaround. I'm not sure what motivates this whole
        | line of argumentation. Sometimes lists are just the best
        | damn thing.
 
        | atq2119 wrote:
        | > 500 loads and dependent stores, at minimum, in the best
        | case hitting L1 cache, which is 3 cycles. So you are
        | talking about 1500 cycles
        | 
        | This is absolutely not how modern CPUs work, and I think
        | you misunderstood where the dependencies are. All the
        | load/store pairs are independent from each other, which
        | means they can be executed in parallel. Which means that
        | this code is throughput limited. Modern CPUs tend to have
        | at least 2 load/store ports, so we're talking a
        | throughout of one copy per cycle, or 500 cycles for the
        | entire operation (plus warm-up time).
        | 
        | Furthermore, this is a pure memmove in many cases, which
        | means a real memmove implementation that has been
        | optimised using vector instructions can be used. Now
        | you're talking about moving 32 bytes per cycle, or 4
        | array entries if they're pointer-sized, which brings us
        | down to 125 cycles plus warm-up. Which is on the order of
        | a miss to memory...
 
        | titzer wrote:
        | Great, now we're talking! I realized in the background
        | that yes, there aren't dependencies between the
        | copies...if the regions of memory don't overlap. But they
        | do overlap, especially if you are just moving them over
        | one element, so the simple analysis doesn't hold. But
        | you're right that's it's just a memmove, so there is an
        | optimized vector implementation (which, incidentally, is
        | probably going to have relatively large setup costs for
        | 500 elements).
        | 
        | And oops, now your vectors are 1 million entries.
        | 
        | I appreciate the discussion. It's a bit of a rathole for
        | something that you shouldn't be optimizing if you can
        | completely avoid it by using the right data structure for
        | your needs.
 
    | kllrnohj wrote:
    | If objects know where they are, why do you need sorted
    | ordering? If you need sorted ordering, it sounds like you're
    | (regularly!) iterating over it. But iterating over a linked
    | list is incredibly slow.
    | 
    | This is where Big-O just really doesn't have a strong
    | relationship with performance. Iterating over a linked-list
    | is O(N), same as a vec. But in the real world it's nowhere
    | close to as fast.
    | 
    | So then if you're not regularly iterating over it a bag
    | implemented with a vec is likely a better option. Then just
    | have a bool that tracks if it's sorted or not, and in the
    | (relatively) rare case you need sorted traversal just sort()
    | it first.
 
      | xigoi wrote:
      | Ordered is not the same thing as sorted. If you do swap-
      | removals, there's no way to sort the array back into the
      | original order unless you also keep track of insertion
      | time, which adds memory overhead.
 
        | kllrnohj wrote:
        | Storing pointers in a linked list also adds memory
        | overhead, too. Insertion time in nanoseconds is only 8
        | bytes - same as a single pointer on 64-bits. So half the
        | memory usage as a doubly linked list.
 
| hawski wrote:
| I still couldn't get or understand an answer to this question:
| 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"").unwrap();
| rewriter.end().unwrap();         }                  assert_eq!(
| String::from_utf8(output).unwrap(),             r#"
"# ); | | 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"").unwrap(); | rewriter.end().unwrap(); } assert_eq!( | String::from_utf8(output).unwrap(), r#"
"# ); | iudqnolq wrote: | You got a pretty good answer on stackoverflow, despite a not | very good question. You could try a follow up question on the | rust discord, but I'd suggest massively decreasing the size of | the code dump, and talking about why the proposed solution | doesn't work. | hawski wrote: | I agree that the question is not very good. It is hard to | make a smaller code dump, when I'm at this stage of learning | the language, because I don't know what matters. | | Answers do not help me to understand the difference and do | not clear the confusion. What they suggested was suggested by | the compiler. | | It does work, when I have a call like that: | func(vec![element!(...)]); | | But if I put the vec outside it will complain about temporary | element!(...): let v = vec![element!(...)]; | func(v); | | Answers and the compiler suggest the following: | let e = element!(...); let v = vec![e]; func(v); | | This however is problematic if you have many elements in the | vec. It also does not answer to me how is that significant. | For me if I can pass a temporary to a function, why can't I | bind the temporary to the vec? The why is important to me. I | would like to make second form work. | | I don't deride people that answered me, I asked a few | clarification comments, but I've got no follow-up or a | follow-up that still doesn't answer the question. It is a | good solution, but I don't understand why I have to go this | way and the obvious one for me doesn't work. | iudqnolq wrote: | I apologize. I skimmed your post and the response and fit | it into some stereotypes. Your question is actually a lot | more interesting than I thought. I couldn't figure it out | reading the docs on my phone. | | When I first tried to reproduce your issue, I got that | `try_new` didn't exist. It's been removed in the latest | version of lol_html. Replacing it with `new`, your issue | didn't reproduce. I was able to reproduce with v0.2.0, | though. Since the issue had to do with code generated by | macros, I tried `cargo expand` (something you need to | install, see [1]). | | Here's what `let handlers = ...` expanded to in v0.2.0: | let handlers = <[_]>::into_vec(box [( | &"a[href]".parse::<::lol_html::Selector>().unwrap(), | ::lol_html::ElementContentHandlers::default().element(|el| | { let href = | el.get_attribute("href").unwrap().replace("http:", | "https:"); el.set_attribute("href", | &href).unwrap(); Ok(()) }), | )]); | | and here's what it expands to in v0.3.0 | let handlers = <[_]>::into_vec(box [( ::std::bo | rrow::Cow::Owned("a[href]".parse::<::lol_html::Selector>(). | unwrap()), | ::lol_html::ElementContentHandlers::default().element(|el| | { let href = | el.get_attribute("href").unwrap().replace("http:", | "https:"); el.set_attribute("href", | &href).unwrap(); Ok(()) }), | )]); | | Ignore the first line, it's how the macro vec! expands. The | second line shows the difference in what the versions | generate. The first takes a borrow of the result of parse, | the second takes a Cow::Owned of it. (Cow stands for copy | on write, but it's more generally useful for anything where | you want to be generic over either the borrowed or owned | version of something.). | | So the short answer is the macro used to expand to | something that wasn't owned, and now it does. As for why it | worked without a separate assignment, that's because Rust | automatically created a temporary variable for you. | | > When using a value expression in most place expression | contexts, a temporary unnamed memory location is created | initialized to that value and the expression evaluates to | that location instead, except if promoted to a static | | https://doc.rust- | lang.org/reference/expressions.html#tempora... | | Initially rust created multiple temporaries for you, all | valid for the same-ish scope, the scope of the call to | try_new. When you break out the vector to its own | assignment the temporary created for element! is only valid | for the scope of the vector assignment. | | I took a look at the git blame[2] for the element! macro in | lol_html, and they made the change because someone opened | an issue with essentially your problem. So I'd say this is | a bug in a leaky abstraction, not an issue with your | understanding of rust. | | [1]: https://github.com/dtolnay/cargo-expand [2]: | https://github.com/cloudflare/lol- | html/commit/e0eaf6c4234af8... | hawski wrote: | No offense taken. The code dump is pretty unfriendly. | | I dabbled with it more around version lol_html 0.2 and | afterwards not that much. Partially, because I felt dumb. | Still I feel like I need to look into documentation | almost for every dot in the code. | | Thank you, especially for your thorough investigation! I | thought that the macro could do something funky, but | didn't know about `cargo expand`. That is a very useful | thing to know. Is there a good write up about such | intricacies like Cow::Owned? I would think that the | problem is probably something that people may have from | time to time. Was there a way to wrap every element in | something to make it work with the old version? | iudqnolq wrote: | To be honest lol_html looks like either someone new to | Rust who got excited by the opportunity to do a lot of | premature optimization or someone with very very intense | perf needs. I strongly recommend to work with owned data | whenever possible and clone freely. | | It's also macro heavy for something that doesn't seem | like it should need to be. It's normal to need to | constantly check the docs with macros, since they're | their own language. | | EDIT: I realized it's by cloudflare. The architecture | makes sense in terms of their needs. | ljm wrote: | While 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. | [deleted] | CraigJPerry wrote: | I'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 wrote: | Logrocket 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 wrote: | Aha! That's quite a neat approach, i wouldnt know what | logrocket does otherwise but i feel pretty aware of their | product these days. | thejosh wrote: | It's a great way to get decent content, I think DigitalOcean | and Linode use (still?) do that. Great marketing tactics to | boost website traffic :-). | EamonnMR wrote: | I 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.' | gspr wrote: | I 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 wrote: | Well, titles are hard. | | All of those things _are_ possible in Rust. But they 're not | easy or approachable. | qznc wrote: | What 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. | pornel wrote: | Rust 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!) | masklinn wrote: | > 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. | rhodysurf wrote: | I 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. | kubb wrote: | Can 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. | _huayra_ wrote: | If you can use C++20, I'd encourage you to look into using | concepts. Since I've started using them, it feels like a much | more natural choice than class hierarchies when one needs to | enforce type constraints on interfaces. | | Instead of making a parent class that has a few key methods you | need (e.g. send, recv) with the signatures (e.g. takes a | pointer, returns a number), one can encode the use of those | functions in a concept (e.g. a "socket" concept). This | decouples the implementation from the use in the method, with | the one big advantage being that it is easily testable -> no | more need to carefully craft type hierarchies to carefully be | able to substitute some mock object when you can just directly | call it! | | OO hierarchies certainly still have their place (even though so | many conference talks seem to be about getting rid of them), | but I'm glad I can relegate them to a dark corner of my toolbox | until I absolutely need them. | | In fact, come to think of it C++ concepts are basically Rust | traits! | rhodysurf wrote: | Yea for sure, it's a lot like swift being protocol oriented | too. It's a major paradigm shift I have to enforce in my head | manually still at this point tho | terhechte wrote: | Note that you can do Swift's protocol oriented programming | / portocol extensions in Rust, too: trait | Test { fn value(&self) -> i32; } | impl dyn Test { fn multiplied(&self) -> i32 { | self.value() * 2 } } | Yoric wrote: | What does `impl dyn` mean, exactly? That this only works | for trait objects? | ComputerGuru wrote: | That it works for any type implementing the trait, via | vtables. Otherwise, it's a method specific to the | particular type implementing the trait (a concrete | implementation but distinct to the specific impl). | ape4 wrote: | Rust could have inheritance - perhaps limit it to single parent | cerved wrote: | I often find the opposite to be true. Where I have to spend a | lot of time clearly defining a hierarchical structure, which | after implementation needs to restructured. Or fighting against | an already defined hierarchy, usually unchangeable and made by | someone else. | inglor_cz wrote: | In 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 wrote: | It'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 wrote: | To 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 wrote: | Unconstrained templates can also sometimes be a massive time | saver. | latch wrote: | I'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/ 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` which is a mouthful - | both in terms of having to declare a type with this, and in terms | of everything is implies/encapsulates. | zxzax wrote: | I just tried that in nightly and it seems to work? Not sure | what the issue with that is. | ptato wrote: | It works, but it isn't what they want. The compiler won't | allow borrowing the str slices from the owned String field. | edflsafoiewq wrote: | Yes it will. https://news.ycombinator.com/item?id=27164504 | Kinrany wrote: | This specific code compiles, you just can't create an | instance of this struct and store references to `phrase` in | `words`. | seoaeu wrote: | Another aspect is that in Rust, all structs have "move | semantics" in that you can copy the raw bytes of a struct to | another location in memory without anything breaking. This is | very nice in general but does rule out creating structs in safe | code which contain pointers to themselves. | volta83 wrote: | The "why is this an issue" has already been mentioned ("what if | `self.phrase.clear()` is called? that creates dangling | references in self.words"), but how to solve it is relatively | straight forward: struct Input { | words: Vec<(usize, usize)>, phrase: String, | } impl Input { fn word(&self, i: | usize) -> &str { let (first, last) = | self.words(i); &self.phrase[first..last] | } } | | Now your Input type doesn't even need to be generic over the | reference 'a anymore (not that it mattered before because that | wouldn't work). | | Now even if self.phrase is resized, you never get memory | unsafety, because the `self.phrase[...]` does a bounds check | and panics on out-of-bounds. So the worst you can get is a | "logic error" in your program that shows up with a really nice | backtrace that's super easy to debug, instead of a miscompiled | program (due to UB) that's often very painful to debug. | | So sure, you don't get "self referential struct with | references" in Rust. But so what? | | The fix for when you want this is always the same: use offsets | like I did above. Offsets are much better than any kind of | pointer, because they allow you to just "memcpy" the struct | around (the address of a field changes on move and copy, but | the offset to that field from the beginning of the struct | always remains constant). | | If you were to use a pointer to a field, you'd need to correct | that pointer during copies and moves. C++ allows this, and this | "feature" has many undesirable consequences for Rust: | | - moves in Rust are O(size_object), moves in C++ can be of any | algorithmic complexity, so any algorithm that moves has to take | that into account this | | - rust collections have one code path, C++ collections have two | code-paths: one for objects that are "simple" to move, and one | for objects that are "hard" to move | | - move constructors that throw in C++ interact with exception | safety in complicated ways | | - probably many many more interactions that I'm missing here | | There might be ways to improve the ergonomics of self- | referential structs in Rust, but given that offsets are simple | and always work correctly without many downsides, whatever | improvement one considers for Rust would probably not be worth | it if it has any of C++ downsides. | petmon wrote: | How would you adjust this approach if the words needed to be | keys? For example, if words were a map from a word to its | count, and you could get the count for a &str? | cesarb wrote: | Another bonus of the offset approach: it can be smaller. The | size of a &str will always be two words (pointer and length), | but if you're sure that your strings will never be longer | than 4 gigabytes, and you're willing to put up with the | necessary integer casts, you can store a (u32, u32), which is | half the size of a (usize, usize) or a &str when your target | uses 64-bit words. And if you're certain that your strings | are never bigger than 65535 bytes, you can go even further | and use a (u16, u16), and so on. | zozbot234 wrote: | > If you were to use a pointer to a field, you'd need to | correct that pointer during copies and moves. | | The "transfer" crate allows for this, though not in a way | that's directly compatible with C++ move. (The latter has | been discussed somewhat in | https://mcyoung.xyz/2021/04/26/move-ctors/ but that's only a | first look at the problem area, not aiming for 100% accuracy | just yet.) | conradludgate wrote: | I've ran into that exact same problem before too. My solution | would be just to have a Vec> instead. Then you can | generate the substrings on the fly when you need to use them | DasIch wrote: | Let's assume for a moment self-referential references were | allowed. Let's further say we have such a struct containing | self-referential references and we move it. | | Moving actually moves things in memory, including the thing | those references reference. This means the references now point | at the old and now incorrect location. Accessing them would | trigger undefined behavior. | | Self-referencing is only acceptable, if the struct is | guaranteed not to move. Rust does provide a mechanism to | provide such a guarantee with std::pin but outside of async | code, you probably don't want to be using it. | | std::pin: https://doc.rust-lang.org/std/pin/index.html | | If you're curious about pinning and have a few hours to spare, | https://www.youtube.com/watch?v=DkMwYxfSYNQ is a great video | that explains it in detail. | edflsafoiewq wrote: | Self-reference is allowed, but it prevents moving. | fn main() { // ok let mut inp = Input { | words: vec![], phrase: String::from("foo bar") }; | inp.words.push(&inp.phrase[0..3]); | inp.words.push(&inp.phrase[4..7]); println!("{:?}", | inp); // this would give "cannot move out of | `inp` because it is borrowed" //let x = inp; | } | emi2k01 wrote: | It also prevents borrowing `inp` or `inp.phrase` as mutable | which makes it useless for most cases. | masklinn wrote: | > I'm pretty sure the compiler inferred a lot less back then | (maybe 5-6 years ago now?) | | If it was post Rust 1.0 then it did not. The more likely case | is the combination of NLL and match ergonomics, which has made | a lot of code way more comfortable, respectively by relaxing | local borrows and avoiding a lot of minutia in pattern matches. | codeflo wrote: | Match ergonomics was one of _the_ greatest improvements to | the language, I'd personally put it almost on par with NLL. | | For anyone who's interested: | | In Rust, you tend to do a lot of pattern matching and | destructuring. But you also often only have a reference to | your data, because copies can be expensive. | | Combining pattern matching and references was always | possible, but confusing: you had to place lots of "ref" | annotations in various places, which I never got right on the | first try. But now, you can magically destructure e.g. a | reference to a tuple into a tuple of references. You don't | have to think about it, it simply works. Combined with Rust's | algebraic types, this allows you to write very elegant, | almost fp-like code that's _also_ zero copy. | thomasahle wrote: | Those match "ergonomics" really tripped me up when first | trying to learn Rust. | | I was trying to figure out references and deref, and I kept | thinking I "got it" and trying to make sure by writing | examples that should and shouldn't work. | | However, because of all the deref "ergonomics" magically | spread around thinks like match, it was really hard to | confirm whether I had the right mental model or not. Lots | of things that surely shouldn't work somehow worked, and I | couldn't explain why. I kept having to go to forums and | people just told me "oh, this is a special magic case we | added". Very frustrating. | masklinn wrote: | Yes, while "match ergonomics" made many "just work", the | magic it implies also obscures the language, and code, _a | lot_. | | I remain of two minds about it. | | If you want to make sure you have the right model you can | always set edition to 2015 tho. You will also lose NLL | and a bunch of other conveniences, but match ergonomics | will be disabled. | steveklabnik wrote: | NLL is in Rust 2015 these days. (As of Rust 1.36.) | codeflo wrote: | I can totally see how this kind of convenience might | hinder learning. For me, the fact that "&a" does so much | magic type conversion stuff had a similar effect. One | thing I sometimes find helpful is to run an IDE (like VS | Code with the Rust Analyzer plugin) and hover over every | single variable to see its type. | | Out of curiosity, since I expect to give a short Rust | introduction in the near future, what was your language | background when learning Rust? How familiar were you with | C-style pointers and/or C++-style references? | nindalf wrote: | I understood the "inference" comment as "the Rust compiler is | more helpful now". Which it is. @ekuber and gang have put a | lot of effort into better error messages over the years and | continue to do so. | drran wrote: | Works for me: #[derive(Debug)] struct | Input<'a>{ words: Vec<&'a str>, phrase: &'a | str, } fn main() { let s="foo bar | baz"; let input = Input { words: | s.split_whitespace().collect(), phrase: s, | }; dbg!(input); } Compiling | playground v0.0.1 (/playground) Finished dev | [unoptimized + debuginfo] target(s) in 1.15s Running | `target/debug/playground` [src/main.rs:13] input = Input | { words: [ "foo", "bar", | "baz", ], phrase: "foo bar baz", } | ptato wrote: | Wouldn't a Box<&'a str> put the reference itself, not the | contents, in the heap? | mplanchard wrote: | Input isn't self-referential in your example, because both of | its fields are referencing the same data owned by the main | function. | | For it to be self-referential, Input needs to take ownership | of the string in phrase, and words needs to then be | referencing phrase. | | You can do this with Pin, but it's not simple. | drran wrote: | Yep, it can be done with Pin and unsafe only, AFAIK. In | theory, Rust can be extended to have relative references, | but it's much simpler to use arrays and indexes and ranges | instead. | | With Pin: use std::pin::Pin; use | std::marker::PhantomPinned; #[derive(Debug)] | struct Input<'a>{ words: Vec<&'a str>, | phrase: Box, _marker: PhantomPinned, } | fn test<'a>(s: String) -> Pin>> { | let bs = s.into_boxed_str(); let mut input = | Box::pin( Input { phrase: bs, | words: Vec::new(), _marker: PhantomPinned, | }); unsafe { let phrase: *const | Box = &input.as_ref().phrase; let words = | phrase.as_ref().unwrap().split_whitespace().collect(); | input.as_mut().get_unchecked_mut().words = words; } | input } fn main() { let s="foo bar | baz".to_string(); dbg!(test(s)); } | danhor wrote: | (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 wrote: | Yeah, 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). | da39a3ee wrote: | > 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. | pron wrote: | I'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. | ta_ca wrote: | didn't lisp prove the opposite? you can go lower than c and | higher than any language we currently have in the same | language? | hu3 wrote: | Yes but where is Lisp market share? It's mostly an academic | language these days. | | If anything Lisp current adoption proves that aiming to | tackle both ends of the spectrum in a single language might | not be a good idea. | ta_ca wrote: | do adoption or market share matter in this context? if you | can have best of the both worlds why would you want to deal | with an another language? | layoutIfNeeded wrote: | Every 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 wrote: | > 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. | nindalf wrote: | To put it simply - Rust empowers a master like dtolnay to | create something as powerful, zero cost, fast and easy to use | as serde ... while also enabling a noob like me to write | simple, relatively bug free code. | | As a bonus I can pull in serde with one line and use it | without worrying about shooting myself in the foot. | pron wrote: | That is _exactly_ what we thought about C++. We, too, | believed that C++ fixed all those problems with both low- | level programming in C, and the "messy" programming of the | "application generators" of the time; it lets you clearly | express intent and yet helps you avoid shooting yourself in | the foot as happens in C. Anyone who truly believes that Rust | is a great language for the average applications developer, | clearly has not met one. | est31 wrote: | I think you have a point that yes, programming languages | depend on the domain a program is in. If the program only | handles up to 10k customers, it can be programmed in | javascript and can indeed keep its entire database in RAM. | sqlite even has a mode for this. A LOT of custom software | is in this "small" domain. However, when you get to | millions or billions, maybe you have to optimize and create | a backend. What should this backend be written in? | Previously the answer might have been C++. But now it's | Rust. | | Personally, being a Rust enthusiast, I think that Rust's | benefits don't just unleash when you think about | performance, but also when you think about large codebases. | Such codebases are usually the scene for refactorings. In a | dynamic language like js, these are extremely hard to pull | off. In Rust, the compiler won't stop yelling at you until | you have cleanly finished the refactor. I _love_ | refactoring Rust codebases, even ones I didn 't write, | while in C++ or other languages it's extremely annoying. | | And note that while C++ enthusiasts might have believed | that everything should be C++, not everyone followed them. | Many people still stayed with C. Many still do even with | Rust around. But there is a small group of people for whom | Rust is such a great improvement that they take the | portability cost and switch to Rust. Linus is heavily anti | C++ for example, but he's open to adding Rust to the | kernel. | golergka wrote: | > However, when you get to millions or billions, maybe | you have to optimize and create a backend. What should | this backend be written in? Previously the answer might | have been C++. But now it's Rust. | | Neither Facebook, nor Instagram or Slack are primarily | built on C++ or Rust though. And many other products that | I can't as confidently remember from the top of my head | aren't. | hu3 wrote: | > However, when you get to millions or billions, maybe | you have to optimize and create a backend. What should | this backend be written in? Previously the answer might | have been C++. But now it's Rust. | | Now it's Rust based on what? Uber backend is almost | entirely built in Go. And so are many other services | handling billion plus requests a day written in other | languages. | | Perhaps Rust advocates know something that Uber | engineering does not. | zozbot234 wrote: | > Anyone who truly believes that Rust is a great language | for the average applications developer, clearly has not met | one. | | What's a better general-purpose language for this mythical | "average" developer? Java/C#? Rust has the same level of | memory safety, and adds data-race safety that these | languages do not have out of the box. Go is in the exact | same boat, btw - memory safe for sequential code, but | concurrent code is tricky since unsafe shared access is | ubiquitous in real-world Go code. Haskell, Agda and Idris | could provide more safety but are not approachable to the | "average" dev. | pron wrote: | > Haskell, Agda and Idris could provide more safety but | are not approachable to the "average" dev. | | You (rightly, I think) place Idris (and Agda, although | that's more a proof assistant than a PL) beyond the reach | of the average developer, and also, apparently, Haskell | -- arguably a simpler language than Rust, but never mind | -- but not Rust. And that's my point and, I guess, our | point of disagreement. If you think that, on that | spectrum between, say, Python and Idris, Rust is at a | good level for the average application developer to the | point where maintaining high-level applications written | in Rust would _ever_ be cost effective, then I think you | are out of touch with the realities of the software | industry and the economic forces that shape it. I thought | the exact same thing as you twenty five years ago: C++ | was _obviously_ the right language for the average | developer -- even more so than, I don 't know, Delphi or | Visual Basic -- wasn't it? | | > What's a better general-purpose language for this | mythical "average" developer? | | I don't believe any low-level language will be cost- | effective for application development for the foreseeable | future, if ever, and the average developer is anything | but mythical (I mean, embodied as a single person, | perhaps, but I'm talking about teams). | | > Rust has the same level of memory safety | | It doesn't (due to much more prevalent reliance on unsafe | as well as FFI), but that's very much beside the point | because, as you acknowledge, safety is not what it's | about. | dathinab wrote: | > Haskell -- arguably a simpler language than Rust | | From a PL research point of view maybe. | | But from a POV of what is simpler to use for a average or | new programmer, Haskell is far worse as far as I can | tell. | | > due to much more prevalent reliance on unsafe as well | as FFI | | Where? In embedded programming? Or programming from | primitives (which I would argue a average programmer | never should do, there are existing libraries)? | | In some use-cases you always end up with a FFI, but that | is also true for other languages. | | Besides that usage of unsafe is both strongly discouraged | and uncommon. | | There are a lot of rust use cases where using unsafe code | is never necessary to a point where people simple ban | _any_ direct usage of unsafe code in their project. | ta_ca wrote: | simplicty is important, but i believe it is not the most | important metric for a PL and have no problem with | complex PLs. the question; is it worth the trouble? c++s | failure is not the complexity but c++ being one of the | least orthogonal languages out there. in c++ you are able | to compose two great things/ideas (other than my only | options are c and c++ this is the biggest reason i prefer | c++ over c) yet the end result is not more but less. | the__alchemist wrote: | I do mostly embedded programming. Performance and ability to | access memory directly is critical! But I like taking advantage | of modern tooling, and abstractions like pattern matching, | traits etc, and clear syntax. Changing languages through an FFI | barrier would add complications. | mjw1007 wrote: | I observe that the last sentence of your first paragraph is | strongly at variance with the Rust leadership's self-image. | | (See for example the "Empowerment, empowerment everywhere" | RustConf 2020 keynote.) | [deleted] | twic wrote: | There's something to this. Rust was built to do low-level | programming well, and as a result it's quite hard. It's | fundamentally more work to do things than in a GC'd language of | similar modernity. That's a perfectly reasonable tradeoff. | | But somehow, Rust got cool, and now a large fraction of its | community wants to use it to write web services. So there's a | lot of gravity dragging the ecosystem, and even the standard | library and the language, in that direction. Even though it is | a poor choice for almost all web services. | | One day, the shine will wear off, and it will be easy to see | that it's a poor choice for almost all web services, and we | will feel some regret over the amount of time and energy that | went into building in that direction. | steveklabnik wrote: | > it will be easy to see that it's a poor choice for almost | all web services | | Time will tell. I used to think this way, but don't anymore. | | That doesn't mean it's right for the majority of them, but I | think it's workable for a very surprising number of them. | twic wrote: | "workable for a very surprising number of them" seems | substantially the same as "a poor choice for almost all" to | me! | steveklabnik wrote: | Time will tell :) | the__alchemist wrote: | Agreed - I built a rust frontend framework, and tried to use | it on the backend for a while. Ended up switching back to | Python and Javascript for web - I use rust for embedded | mostly now. | hu3 wrote: | Like Uber, some do manage to avoid the pitfall of trying to | shoehorn hard languages everywhere. I recently learned their | microservice fleet is almost entirely in Go [1]. Could also | have been C#, Java, Python or Node but the point is it is not | Rust or C++. Probably for the reasons you stated. | | [1] https://news.ycombinator.com/item?id=27120689 | geofft wrote: | "Software should be developed by small elite groups" is pretty | much the opposite of the Rust developers' goal. I do agree with | you that it's a bad goal to have, which is why I'm optimistic | about Rust's success. | [deleted] | himujjal wrote: | Rust is not at all a high-level language in any sense and will | not replace the places Kotlin, Go, TypeScript etc occupy. If I | wanted to write a super-fast server with some time constraints, | I would choose Go without thinking twice. Go is fast enough. | Rust is only needed when you need things to be super-duper fast | and memory efficient. Those two criterias are the only reasons | to use Rust. | llogiq wrote: | Well, I have been writing both high and low level code in Rust, | and I must admit I've been happily falling into that "trap". | The nice thing about Rust for high level software is that it | lets us specify interfaces in a way that makes them actually | usable without delving into the low level details. | | A lot of things that are hand waved away in C++ (who owns this? | How long does this need to be alive? Can I mutate this?) are | completely explicit in a Rust interface. And Rust's type system | is strong enough to express a lot of rules that no one would | want to write down in C++ (because overriding them is just one | cast away), allowing us to write hard-to-misuse libraries. | | Coding on top of such libraries is a really fun experience. | That's why I call Rust an all level language. | pron wrote: | Different languages appeal to different people, and there is | no doubt some individual programmers will find Rust appealing | even for application programming, just as some found C++ | appealing for similar purposes. But a CIO of, say, an | insurance company who decides that internal applications will | be written in Rust would be making a very imprudent financial | decision. | benreesman wrote: | Rant 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! | golergka wrote: | Honest question: what views does one has to have to doesn't | take non-browser JS seriously, and at the same time use Python? | And what's unfortunate about node.js compared to it? | lstamour wrote: | I can say that if someone is sold on Python, you're not going | to convince them to move to Rust but you might convince them | to move to Go. Why? Go provides better performance than | Python but without the language and keyword complexity of | Rust. There's less language to learn. | | That said, the more interesting question is TypeScript vs | Rust. I can see how the complexity of C++ naturally leads | folks to Rust. But I've had a hard time choosing Rust over | TypeScript when trying to implement a basic service. | | It's absolutely true that Node.js web servers aren't | optimized for multi-threaded data access times and the | Node.js event loop can get in the way of responsive | performance. | | But at the same time once you build, for example, an HTTP 1.1 | web server for Node that is similar to what you'd build using | semi-unsafe code in Rust and ships by default in Go standard | library, it's hard to say that JS is any less efficient than | Rust when both are generally written to use async/green | thread runtimes, make calls using a pool of Postgres or | database connections, and either have PG return JSON or | otherwise reshape and assign the results to a data model. | | Given more computing power, Rust obviously wins, but if | you've a single thread to work with, and a small buffer so | after each web request is served, GC can do its thing... | where are the tangible Rust benefits besides maximum speed? | | I suppose I'm cheating by suggesting you could use Actix to | power your Node application, but the problem I'm facing is | that Rust is really complicated to learn and there are few | frameworks that make it easier. The amount of published and | shared knowledge there is around Go, PHP, TypeScript, Python | and Ruby is enormous. Yes, Rust is clearly easier to learn | than all the warts of C++, but it feels like Rust hasn't | gained enough traction to convert TypeScript fans, for | example, or at least those building APIs and front ends. | | I'd really like to be convinced to use Rust because of how | easy it is compared to TypeScript but I haven't seen anyone | actually suggest that in practice. Go? Yes, it's easier to | learn than to understand both JS and TS and you can put up | with the lack of generics by having more verbose code | compared to TypeScript. But Rust? Is it just the case that | these other languages are more mature such that all the blog | posts have been written? | | Also note that the hardest problem when getting started in | any new language is package and framework selection. Unless | you follow a book that has already picked a handful of | packages such that you're happily falling into the pit of | success, I recognize that all languages (except maybe Rails | or Spring) force the user to make hard choices upfront about | their dependencies. But the only book I've seen that presents | a coherent narrative for Rust APIs is Zero2Prod[1] and I'd | feel more confident if there was at least one other similar | set of learning materials. | | 1. https://www.lpalmieri.com/ for the book published as blog | posts, https://www.zero2prod.com/ to buy. | benreesman wrote: | Python has (more than one actually) trivially seamless FFI | stories down to native (read: C++) code. I personally like | `pybind11`, but there are others. Python is basically a | convenient way to invoke numpy or Tensorflow or Torch or | whatever. It's got numpy/scipy/sklearn/BLAS bindings etc., | and is for better or worse how you do scientific computing in | the mid-to-upper-mid range these days (the HPC folks have | their own whole story). | | It's a pain in the ass in it's own way, but you're kinda | stuck with it if you want to do mainstream ML. | | As a language it's dumber in some ways that JS, definitely | slower in some cases, but also way less weird. Prototype | chains and stuff are a cool demo that BE knew about Self and | shit in the week he had to write JS, but anyone who claims | they like that has Stockholm's syndrome worse than someone | who thinks Ruby metaclasses make sense. | mrighele wrote: | I've been using python for many years, but these days I | would prefer to use JS (or better, TS) even for backend. I | find it more enjoyable to use than Python, and faster. Sure | it has its warts, but if you write idiomatic code they are | not much of a problem (e.g. I don't think there is a single | class in the code I write, so prototype chains are a non- | issue) | cletus wrote: | So what I like about your comment is you're clearly more | pragmatist than zealot or ideologue. | | So I don't know Lisp or Haskell. I never learned in the college | years when I might've had time and now I just don't want to pay | the years long learning curve. I've got shit to do. | | But the big problem (for me) is I've completely turned off | dynamic typing. Python did this to me and i I honestly don't | think I'll ever go back. I refer to dynamic typing as "unit | tests for spelling mistakes". YMMV. | | Another issue is I like simple grammars and relatively | opinionated languages. I'm not the biggest fan of GC either. | Years of tuning GC pauses of JVM have soured me on the | boundless benefits of GC. | | I give this as context to say that I like Go. You can learn Go | in a really short amount of time. There's usually only one way | to do things in Go. That's good. Less surprise and less | arguments in teams. | | An issue I have with C++ (and this applies to Lisp and Haskell | too) is it allows people to be too clever for their own good. | Look at 200 lines of Lisp and there might be a C compiler | buried in there. Who knows? That might demonstrate the power of | the language but is usually counterproductive to a team or | project. | | So memory safety is the big selling point of Rust. You need to | buy into that and why things like static typing are good even | though you might struggle with the type system and borrow | checker at first. | | Is better to fight the compiler than be surprised by the | runtime (IMHO). | | But Rust of course is not issue free and it differs from some | long-standing and unfortunate design choices early in its life | that are going to be hard to correct. Build times are a big | one. See this [1] for more. | | For the last few years I've written Hack (aka PHP) | professionally. One thing one comes to really appreciate is | cooperative async-await, which is pervasive. Writing C/C++/Java | has taught me that of you every manually spawn a thread you're | going to have a bad time so you should do anything in your | power to avoid this. Go is one flavor of alternative. Hack is | another. I'm not sure I'ma big fan of Rust's. | | Ultimately, with Rust out any other language for that matter, | you need to learn and buy into the idioms otherwise you're | going to have a bad time. | | [1]: https://pingcap.com/blog/rust-compilation-model-calamity | atoav wrote: | I had an awful time when I started using Rust because I had a | strong (object oriented) view of how to structure my code. At | one point where nothing seemed to work and I didn't manage to | get anything to compile I just was like: "ok, I give up Rust, I | will do it your way". | | From then on everything worked surprisingly easy. | | The 3 main things that make Rust great (besides thenusual | selling points) are IMO: | | - the library manager/dependency managment is great | | - Even if you are never gonna use Rust again, some of the | lessons you have to learn when learning Rust are universally | useful. Thinking about ownership and race conditions the Rust | way will certainly help you tackle some parricularily hard | issues with multithreaded code in other languages as well. | | - The amount of things you can rake as a given within Rust code | is very high. If it compiles it usually works. If it doesn't it | gives helpful indications why. These indications help you to | understand your code better. | giantandroids wrote: | After years of dynamically typed / interpreted languages, I | decided I wanted to learn a statically typed language and so | I to set out to trial both Go and Rust. Rust was a painful | experience. I kept trying to pass around simple types and | constantly hit compiler failures. | | I then set up Go and got started. Fell in love with it, I was | immediately 10x more productive then I was with rust. I also | found reading other people's code far more easier as well, | where rust would have all this (for me) difficult syntax. | | I want to like rust, but I honestly don't think I am smart | enough. | [deleted] | lalaithion wrote: | Coming from dynamically typed high level languages, you're | learning two things at the same time when you're learning | rust: | | 1) Low level programming 2) Typed programming | | Both of which are difficult. | | I would suggest learning one of Haskell or Scala and one of | C or C++ first, and then trying Rust again. | stouset wrote: | Rust is sometimes a painful experience because it forces | you to deal upfront with an important concept other | languages play fast and loose with. | | You can absolutely get productive more quickly in other | languages as a result. But to me, developers who take the | time to get over that hurdle by and large become | dramatically more productive in the long run, and that's | before you take into account the fact that their code will | have near-optimal performance and be virtually bulletproof. | | There's a reason we're regularly seeing best-in-class | general purpose tools and libraries coming out of the Rust | ecosystem. | benreesman wrote: | For a post that started with "Rant Disclaimer", this has | generated kilobytes of useful, insightful responses, and I'm | going to be looking for spare time everywhere I can find it to | follow up on all the great resources. I do intend to reply to | everyone's comments, but the 20 of you are outrunning the 1 of | me on that. If it takes me a few hours to reply thoughtfully to | your thoughtful replies please bear with me. | steveklabnik wrote: | While some people complain about the Rust community being | evangelical, you'll find this is common. We've built a | culture of helping answer questions, sometimes to the point | of being too enthusiastic about it. If you ever need help, | jumping into the forums or various chat platforms will | usually get you a courteous answer in minutes. | | We aren't perfect, but this is one of the aspects of the Rust | community I'm most proud of. | stouset wrote: | To be completely honest, I think you've had a bigger impact | than most in making the community that way. | | You were a huge boon to the Ruby/Rails community, and | you've taken that experience and been instrumental in | building a helpful, enthusiastic, and friendly community | around Rust. One of these days I hope our paths cross so I | can buy you a drink. | GuB-42 wrote: | Maybe Rust is not for your project, it is not a universal | language, far from it. It is a language designed to make a web | browser, and some projects have similar requirements, which | makes Rust good for these projects too, but for some others, | well maybe the much hated C++ is just better. | | There is a good reason why several programming languages exist. | Glavnokoman wrote: | Sounds 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 wrote: | What's the link that takes me down the Zig rabbit hole? | matheusmoreira wrote: | The Road to Zig presentation: | | https://youtu.be/Gv2I7qTux7g | | C but with the problems fixed. | benreesman wrote: | Queued. Thank you! | gameswithgo wrote: | Rust gives you c++ runtime performance with compile time memory | and data race safety. that is about it. less warts | syntactically just by being newer. | the__alchemist wrote: | For me, it's the overall experience. Ie: auto-generated docs | for libraries, nice package manager, built-in linter and | formatter, easy-to-install toolchain[s], high-level coding | patterns etc. | benreesman wrote: | That's a fair-sounding point. Java had this (but also a bunch | of other problems). | devit wrote: | It's the only language existing that is memory safe and | provides zero cost abstraction to the CPU capabilities (i.e. no | mandatory GC, values don't need to be boxed, ability to mutate | memory). | | The only limitation that may not eventually be removed is the | lack of dependent types (although you can kind of emulate them | by lifting terms to the type level and using lifetimes to | represent variables). | | C++ is not memory safe, Lisp has no type system and Haskell has | GC, all values are boxed in a closure and it cannot mutate | memory safely, so none of these languages are even in | contention. | [deleted] | benreesman wrote: | C++ compiled by clang-12, with clang-tidy and cppcheck turned | all the way up, with everything owned by a std::unique_ptr, | run through ASAN, UBSAN, TSAN etc (which seems to build about | as fast as Rust) lets what by that Rust catches? | | I'm not being sarcastic, in all earnestness educate me! | MaxBarraclough wrote: | What you've described is a way of catching _some_ memory- | safety problems in C++ codebases. There 's no easy way to | catch them _all_. Even Chromium is riddled with memory- | safety issues that result in serious security | vulnerabilities. [0][1] We don 't have a practical way of | writing large and complex C++ codebases that are entirely | free of memory-safety issues. | | I don't know enough about Rust to comment on whether it | does a better job than C++ on reference cycles, but my | suspicion is that it does. | | [0] https://www.chromium.org/Home/chromium-security/memory- | safet... | | [1] Related discussion: | https://news.ycombinator.com/item?id=26861273 | benreesman wrote: | Chromium is a *way* legacy codebase, I think WebKit goes | back to like Konqueror or something? Chromium is a very | weird example to cite for modern C++ vs. modern Rust | memory safety. | | AFAIK avionics software is still largely written in Ada, | because it won't let you fuck up meters vs feet type | stuff. And if someone said: "Rust has a slam-dunk niche: | we're going to crank static analysis past helpful to | downright intrusive because sshd simply can't buffer | overflow", I'd be like, yeah, ok. | | But at the time I stopped using it, Alacritty couldn't | handle meta keys on Big Sur, and I wanted to fix that, so | I spent a weekend or two that I really couldn't spare | trying to unfuck it, but between `print` not being | obvious (because someone had already borrowed the thing I | wanted to print out) and the build being slower than C++ | I timed out. | cvwright wrote: | OTOH Chrome has one of the best teams in the world | working on it, funded by one of the richest companies in | the world, with the best tools. And they take security | very seriously. | | If they can't get it right, who can? | MaxBarraclough wrote: | A good point, but I imagine benreesman's counterpoint | would be that things might be different if Chromium were | written entirely in _modern_ C++, strictly following | modern best practices. | | My suspicion is that this is too optimistic, but I can't | really substantiate it. | | What would be a good security-sensitive modern C++ | codebase, ideally from a high-profile source like Google, | to compare against? | fnord123 wrote: | Cool that you use all those tools. Wouldn't it be cool to | be able to depend on libraries knowing they all use those | settings? And you can depend on libraries by putting a | single line in a toml file. | | I mean C++ dependency management is so bad there is a | concept of a header only library so you just have to copy | the file into your project with no support for managing | versions. | | C++ dependency management is so bad people use the OS | installed dependcies because they struggle to set up a | build environment otherwise. | | What I'm saying is that C++ is pretty good and toe to toe | C++ with all the tools you describe and Rust are fairly | comparable. But with Rust the whole ecosystem uses it so | the network effects make things much better. And it's | snowballing. | benreesman wrote: | You make a really good point about C++ headers. Textual | inclusion of globs of bytes into translation units is | 1970s legacy stuff that totally fucks up reasonable build | times, and for all the talk about C++20 modules they | haven't delivered yet. | | I actually think this is one of the things that Rust | could crush C++ on, because build times are becoming the | whole show on big C++ projects. | | It's super weird to me that C++, with this whacky 1970s | constraint that totally fucks up modularity, still builds | neck-and-neck or better with Rust. | | In principle Rust could do *way* better on this, and that | is a feature that might get me to consider Rust | seriously, no matter how stupid I think the borrow | checker is. But Rust still compiles dog slow... | conradludgate wrote: | I find if you're aware about how you define your modules, | incremental compiles are usually pretty quick. Yes a | complete build can take a while but tools like sccache[0] | can help with that in CI pipelines and when getting a new | dev environment up. | | [0]: https://github.com/mozilla/sccache | fnord123 wrote: | >It's super weird to me that C++, with this whacky 1970s | constraint that totally fucks up modularity, still builds | neck-and-neck or better with Rust. | | Not so fast! The compilation and linking is neck and neck | with Rust so you can have an ELF in the same amount of | time. But then you have to run all the C++ tools you | mentioned to have parity with what the Rust compiler is | doing. | | > for all the talk about C++20 modules they haven't | delivered yet. | | Even when they're delivered, will all the libraries you | depend on use them? | | >In principle Rust could do _way_ better on this, and | that is a feature that might get me to consider Rust | seriously, no matter how stupid I think the borrow | checker is. But Rust still compiles dog slow... | | The team that built the Borrow Checker deserves the | Turing Award. It is an outstanding bit of computer | science and engineering. I'm surprised that you think | it's stupid. | | It used to be dog slow but it's gotten very fast in the | past two years. Another achievement. | | >In principle Rust could do _way_ better on this, and | that is a feature that might get me to consider Rust | seriously | | Modules? Your take away from all this is that modules are | the killer feature of Rust and that the Borrow Checker is | stupid? | | I call shenanigans. You are an elaborate troll. Well done | for getting two responses out of me. | galangalalgol wrote: | I have the opposite problem with rust dependency | management, its too easy so you get deep widely branching | dependency trees by differing authors with differing | licenses. From a corporate use perspective this is a | legal nightmare. From a security standpoint, this is a | trust nightmare. | Keyframe wrote: | For licenses there's cargo-deny | https://github.com/EmbarkStudios/cargo-deny | | On the security standpoint and a wide net of crates.. | that's a problem, same as with npm/yarn/pip/whatever and | I agree on that. That one bugs me as well. Difficult to | audit. | galangalalgol wrote: | Cargo-deny is accurate, but it accepts no legal or | financial risk if it was in some case wrong and you | infected a codebase with gplv3, and thus corporate | lawyers everywhere ignore it. | | Edit: this post keeps getting up and down voted. I | suspects it has to do with the phrase infected by gplv3. | I like the license fine, but being realistic many | businesses treat it like radioactive waste, so that has | to be a consideration | benreesman wrote: | In fairness, you have those problems no matter how you | include other people's code. At least Cargo as far as I'm | aware uses a SAT-solver to deliver you a working dep | graph unlike `pip` or `npm` or any of that nonesense. (as | long as I remember correctly that Carl Lerche wrote that | part. Sidebar: wicked smart guy). | galangalalgol wrote: | You do, but clunky dependency management meant fewer less | granular dependencies. | andrewaylett wrote: | This stack would catch _most_ problems, but not necessarily | at compile time. It's unsound, because it's still possible | to write data races. And it's possible that two components, | each on their own correct, combine to make something that | isn't correct. | | Rust's borrow checker makes it impossible to write code | containing a data race. It does this by only accepting a | subset of possible safe programs, but this is (or at least | may be) a reasonable trade-off due to the guarantee you | get. | | It's not that you can't write safe code in C++, but you're | always at the mercy of your validation to try to have | confidence that you've actually managed to do what you set | out to do. While Rust obviously doesn't preclude bugs at | all, but it does enforce that you _can't_ write a certain | class of previously-prevalent bugs. | | I think this blog post from Mozilla does a good job of what | that means in practice: https://blog.rust- | lang.org/2017/11/14/Fearless-Concurrency-I... | jandrewrogers wrote: | > It does this by only accepting a subset of possible | safe programs, but this is (or at least may be) a | reasonable trade-off due to the guarantee you get. | | I think this is correct but would expand on the nature of | the tradeoff. Rust only accepts a subset of safe | programs, and can express any program in the abstract, | but that subset may not contain the _optimal_ safe | program. In some cases, differences in practical | performance between the _expressible_ safe program and | the _optimal_ safe program can be quite large. | | Modern C++ cuts a tradeoff along a different dimension: | it will accept unsafe rubbish but the optimal safe | program is also expressible in virtually all cases. C++ | has large niches where it can express optimal safe | programs, and where this optimality is important, that | Rust cannot. Obviously it is incumbent on the programmer | to write safe code in C++, but the flexible type system | is more capable of enforcing safety than I think people | expect, particularly in recent versions of C++. | imtringued wrote: | Isn't that a complete drag? Needing to install a dozen | different tools just to do something basic as maintain | security? There is no way you can hold every C++ project up | to this standard. If you asked me to write software in C++ | I would forget to use half the tools you listed and your | list isn't even complete to begin with! | | Of course this is not an answer to your comment. | | Even if you use std::unique_ptr you can still run into use | after free bugs. std::unique_ptr is just heap allocation | with a "stack allocation style" lifetime. You can still run | into use after free by putting a reference to stack | allocated data (this applies to std::unique_ptr as well) | into a struct or global variable that outlives the stack | allocated data. std::unique_ptr isn't really meant to solve | use after free, it exists to ensure that heap allocated | data is properly freed eventually. It saves you the | discipline to match every new with a free but nothing more. | | This is something that you can only solve with a borrow | checker. If you were to add the ability to detect these | types of problems in C++ then you would have essentially | added a borrow checker to C++. | fluffything wrote: | Does that warn if you do: | | vector v{1,2,3,4}; for (auto&& i: v) v.push_back(i); | benreesman wrote: | In my little toy build I set up no, it doesn't warn me | that smashing a data structure while iterating over it at | the same time gives UB. I'm 50/50 on if there's a flag | for that, don't actually know. | | Again, in the spirit of becoming more educated, what's | the equivalent Rust code? I have `rustc`/`rustup` on my | box so I can run that too. | paavohtl wrote: | This should be about equivalent. It does not compile, as | expected. https://play.rust- | lang.org/?version=stable&mode=debug&editio... | gravypod wrote: | Another fun feature of rustc is you can execute this | command: rustc --explain E0502 | | It gives you a small example of what the error is, | explains it, and tells you how to fix it. | | This is a very nice feature for new people (like myself). | benreesman wrote: | That's definitely useful, and I'm not completely | mystified by the notion that simultaneous borrowing of | something as both mutable and immutable is something that | Rust watches for. | | In C++ you (usually) do this with `const`, which is | fairly low friction, very statically analyzable, and I'm | a little unclear what bug hardcore borrow-checker static | stops me or my colleagues from making that `const` can't | catch? | adwn wrote: | > _In C++ you (usually) do this with `const`, which is | fairly low friction, very statically analyzable, and I 'm | a little unclear what bug hardcore borrow-checker static | stops me or my colleagues from making that `const` can't | catch?_ | | Rust catches the bug where you forget to use `const`, | because the C++ compiler doesn't force you to use it. | Your argument boils down to "I don't need Rust's borrow | checker, I just have to remember to use a dozen tricks, | follow various patterns and guidelines, run several | static analyzers and runtime checks, and I'm almost | there". | | This is not meant as an attack against you. I used to | program in C++, and I'm mostly using Rust now. The borrow | checker frees up those brain resources which had to keep | all those tricks, patterns, and guidelines in mind. | [deleted] | whb07 wrote: | Here's a simple point for you to consider. Let's assume | you and your team are 10x devs with perfect knowledge and | use all the right tools. What happens when you guys leave | or someone who isn't up to your standard contributes, or | is a junior dev? | | Imagine all the best tools and all the best and memory | safe features all rolled up to one called Rust. | | Lastly, with C++ there are many situations where the | memory unsafe actions are not caught until they are | triggered dynamically. So like others have mentioned, | you'd have to have the best test suite combined with the | best of fuzzing etc. | | Or you can just use Rust where nearly everything is | caught at compile time with useful errors and when | something funky does happen at runtime it will just panic | before you cross into UB/ unsafe land | electrograv wrote: | Const in C++ is _entirely unlike_ immutability in Rust. | Even ignoring the existence of "const_cast", C++ const is | vastly weaker than Rust immutability in several | _extremely fundamental ways_ that should be immediately | obvious if you're a C++ professional with even a surface | level introductory understanding of Rust. At this point | you honestly should just take some time to learn a bit of | Rust, and it will likely all make sense. If not, it's | possible you're relatively amateur C++ developer (no | shame in that) lacking experience of the many ways in | which C++ const falls very short. | | But to directly answer your question, suppose I have a | "struct FloatArrayView { float* data; size_t size; };" Of | course this is a toy example, but humor me for now and | consider the following: | | 1. Without excessive custom effort (e.g. without manually | unwrapping/wrapping the internal pointer), how do I pass | it to a function in C++ such that the data pointer within | becomes const from the perspective of that function (i.e. | you'll get a compile error if you try to write to it?) | Hint: It's very complex to do this in C++, vs trivially | easy in Rust. And no, passing as const or const reference | to "FloatArrayView" does NOT work. The only solution in | C++ for complex composite types operating with "by- | reference semantics" is ugly, complex, and horribly error | prone to maintain correctly. For a more concrete example, | consider how "unique_ptr" works with the constness of the | value it holds. A "const unique_ptr" is NOT a unique | pointer to const T. A "unique_ptr" is, but the | relationship and safe conversions between these are not | simple or easy to implement or even use in many cases. It | gets even worse when you need to implement a "reference | semantics" type like this that is inappropriate to be a | templated type, or contains a variety of internal | references unrelated to the template arguments. | | 2. Now suppose I solve #1 and pass this into some class | method, which then stores a copy of the const reference | for later. But I as the caller have no way of knowing | this. So after that method returns, I later proceed to | modify the data via my non-const reference (which is a | completely valid operation), but this violates the | previous method's assumption that the data was immutable | (will never change). This creates an incredibly dangerous | situation where later reads from that stored const | reference to data (that was assumed to be immutable) is | actually going to actually yield unpredictably changing | results. _Const is not immutable._ Question: How do you | make it so C++ guarantees that passed reference is truly | _immutable_ and will never be mutated so long as the | reference exists, _and enforce this guarantee at compile | time?_ In Rust this is easy (in fact, it's the default). | In C++, it is impossible. The closest you can get in C++ | are runtime checks, but that's nowhere near as good as | compile time checks. | | Edit: Removed a bunch of perhaps unnecessary extra C++ | trivia which I'll save for later :) | jcelerier wrote: | How would one make e.g. in Rust then ? | #include int main() { | std::vector foo{1,2,3,4,5}; | foo.reserve(foo.size()* 2); for(auto it = | foo.begin(), end = foo.end(); it < end; ++it) | foo.push_back(*it); } | oconnor663 wrote: | Other comments have answered this specific question, and | I think it might be interesting to look at a similar- | looking question that's actually more problematic for | Rust. What I'll ask is, what's the Rust equivalent of | this: #include | void do_stuff(int &a, int &b) { // stuff | } int main() { int my_array[2] = | {42, 99}; do_stuff(my_array[0], my_array[1]); | } | | That is, how do we take two non-aliasing _mutable_ | references into the same array /vector/view/span at the | same time. (To be clear, none of the following applies to | shared/const references. Those are allowed to alias, and | this example will just work.) Notably, the direct | translation doesn't compile: fn main() | { let mut my_array = [42, 99]; | do_stuff(&mut my_array[0], &mut my_array[1]); } | | Here's the error: error[E0499]: cannot | borrow `my_array[_]` as mutable more than once at a time | --> src/main.rs:7:32 | | 7 | do_stuff(&mut my_array[0], &mut my_array[1]); | | -------- ---------------- ^^^^^^^^^^^^^^^^ second | mutable borrow occurs here | | | | | | first mutable borrow occurs here | | first borrow later used by call | | = help: consider using `.split_at_mut(position)` or | similar method to obtain two mutable non-overlapping sub- | slices | | The issue here is partly that the compiler doesn't | understand how indexing works. If it understood that | my_array[0] and my_array[1] were disjoint objects, it | could maybe deduce that this code was legal. But then the | same error would come up again if one of the indexes was | non-const, so adding compiler smarts here wouldn't help | the general case. | | Getting multiple mutable references into a single | container is tricky in Rust, because you (usually) have | to statically guarantee that they don't alias, and how to | do that depends on what container you're using. The | suggestion in the error message is correct here, and | `split_at_mut` is one of our options. Using it would look | like this: fn main() { let | mut my_array = [42, 99]; let (first_slice, | second_slice) = my_array.split_at_mut(1); | do_stuff(&mut first_slice[0], &mut second_slice[0]); | } | | However, other containers like HashMap don't have | `split_at_mut`, and taking two mutable references into | e.g. a `HashMap` would require a different | approach. Refactoring our code to hold i32 keys instead | of references would be the best option in that case, | though it might mean paying for extra lookups. If we | couldn't do that, we might have to resort to | `Rc>` or trickery with iterators. (This | comment is too long already, and I'll spare the gory | details.) | | At a high level, Rust's attitude towards multiple- | mutable-references situations is leaning in the direction | of "don't do that". There are definitely ways to do it | (assuming what you're doing is in fact sound, and you're | not actually trying to break the aliasing rule), but many | of those ways are advanced and/or not-zero-cost, and in | extremis it can require unsafe code. Life in Rust is a | lot easier when you refactor things to avoid needing to | do this, for example with an entity-component-system sort | of architecture. | devit wrote: | Use a crate that provides safe functions implemented with | unsafe code to do that, like | https://docs.rs/splitmut/0.2.1/splitmut/ | oconnor663 wrote: | Neat! I bet we could add a macro to that crate to make it | work with any (static) number of references. A variant of | this using a HashSet to check arbitrary collections of | keys might be cool too. | Lev1a wrote: | The most short and elegant example I could come up with | that actually worked (since the example above really | shouldn't work in Rust in the first place): | fn main() { let mut v = vec![1, 2, 3, 4, 5]; | let new_size = v.len()*2; v = | v.into_iter().cycle().take(new_size).collect(); | println!("{:?}", v); } | | https://play.rust- | lang.org/?version=stable&mode=debug&editio... | paavohtl wrote: | There are a couple of options: | | - Repeating the vector using the built-in method | `Vec::repeat`. This allocates a new vector, but `reserve` | is likely to do the same, just implicitly. | | https://play.rust- | lang.org/?version=stable&mode=debug&editio... | | - Using a traditional for loop. There are no lifetime | issues, because a new reference is acquired on each | iteration. The reserve is not required, but I included it | to match your C++ version. https://play.rust- | lang.org/?version=stable&mode=debug&editio... | | - Creating an array of slices and then concatenating them | into a new Vec using the concat method on slices: | https://play.rust- | lang.org/?version=stable&mode=debug&editio... | namibj wrote: | let mut v = vec![1,2,3,4]; for i in v.iter() { | v.push(i);}; | | And that doesn't work, because the v.iter() is sugar for | Vec::iter(&v), while the v.push(i) is sugar for | Vec::push(&mut v, i) . I think it'd use deref coercion on | the i, as Vec::iter(&v) gives you `i: &isize`. If this | wasn't ints (or other `Copy` types, for that matter), | you'd need to use .into_iter() to consume the Vec and get | ownership of the entries while iterating, or use | `.push(i.clone())` because `Vec::push(&mut self, T)` | is the signature, and you can only go automatically from | `&T` to `T` for `Copy` types. Actually, it _may_ even | need `v.push(*i)`, thinking about it. Try on | https://play.rust-lang.org | benreesman wrote: | So I don't have the same intuition for desugaring `vec!` | that I do for desugaring `for (auto&& blah : blarg)`, but | in either case if you desugar it the problem becomes a | lot more clear. The Rust borrow checker errors I'm sure | become second nature just like the C++ template | instantiation ones do, but that is faint praise. To get | some inscrutable C++ template instantiation error you | have to mess with templates, and that's for people who | know what they're doing. In Rust it seems like the borrow | checker isn't for pros, it's C++-template complexity that | you need to get basic shit done. | | C++ is actually a pretty gradually-typed language, and | I'm in general a fan of gradual typing. I don't mind that | some people prefer BDSM-style typing, but IMHO that goes | with GC e.g. Haskell a lot better than it does with | trying to print something out or copy it or whatever. | zxzax wrote: | It's not the same as C++ template errors. This is | something that will directly cause a segfault in your | code, that the Rust compiler is able to catch; AFAIK no | C++ compiler would be able to catch that. | divs1210 wrote: | just code in assembly then. | | Batteries included with safe defaults is a BIG feature. | | Legacy C/C++ code is littered with so many memory related | bugs that big companies are funding people to rewrite | essential unix utils in Rust. | benreesman wrote: | No argument that legacy C/C++ code is Swiss cheese on | 1980s memory bugs in a lot of cases. | | Modern C++ vs. modern Rust. Why is modern Rust better? | Legacy C++ can be transformed into modern C++ is a semi- | mechanical, semi-you-emply-someone-good-at-emacs way, at | a cost in time, money, and risk way less than empty | editor. | CJefferson wrote: | There are, for me, two problems with ASAN and friends: | | 1) it requires a later execution, rather than your code | failing to even compile. | | 2) it requires excellent and complete tests, probably with | quality fuzzing, as often the corner cases (like hiting | buffer size limit) is where problems occur. | | For me, the argument for Rust is exactly that it let me | (mostly) get rid of ASAN and UBSAN and valgrind and | friends, which I consider required for C++. | | The other big advantage of Rust is package management, with | I feel C and C++ are awful at, particularly if you want to | release for Linux, Mac and Windows. | benreesman wrote: | The package management point is completely on point. | CMake blows and mixing in autotools makes it worse. | | I think I consider the need to run under ASAN in a | configuration that gets code coverage a feature rather | than a bug. It's stupid monkey-brain stuff, but the | forcing function of needing to be able to drive your | critical path at will in my experience leads to better | outcomes. | tialaramex wrote: | Composability bites you badly here. You need that ASAN | run, with full coverage, for the entire C++ system, any | time any part of it changes, even if the changed element | seems irrelevant and clearly safe, the way C++ is defined | that doesn't mean it didn't make the whole system unsafe. | | But since Rust's Memory Safety guarantees apply to | components, the composition of those components also has | Memory Safety by definition. | | If you have one guy writing incredibly scary to-the-metal | unsafe Rust to squeeze out 1% more performance from a | system that's costing 85% of your company's burn rate, | you can put that tiny component through ASAN hell, | looking for any possible cracks in its Memory Safety with | all sorts of crazy input states. | | But the sixteen other people in the team writing stuff | like yet-another REST JSON handler entirely in Safe Rust | don't need that effort and when you compose all these | pieces together to build the actual product, you don't | need ASAN again, you checked that the scary bit was safe | and you're done. | oconnor663 wrote: | ASan, UBSan, etc. are absolutely life-changing, no doubt | about it. But there's a pretty big difference between | catching things statically, and catching them at runtime. | If you have an issue that, say, only triggers on odd- | numbered Tuesdays on Windows 7, your tests suite is | probably going to miss it, and ASan isn't going to be able | to help you. | | Here's an example from one of Herb Sutter's talks, which I | refer to frequently. He describes an issue that can come up | with shared_ptr in reentrant/callbacky code, and the | punchline is that you have to be careful never to | dereference a shared_ptr that might be aliased: | https://youtu.be/xnqTKD8uD64?t=1380 | | I also just wrote a giant example of my own :) in reply to | your toplevel comment. | https://news.ycombinator.com/item?id=27168368. TSan will be | pretty good at catching some of the simple variants of | this, like where you totally forget to take a lock. But | it's going to have a hard time with cases where you "stash" | a reference longer than you should, because later uses of | the reference might _appear_ safe for a while yet become | unsafe down the road with unrelated code or timing changes. | | Another issue with ASan and UBSan, is that every | application has to run them (and maintain its own test | suite comprehensive enough to make them useful). With | static checks, a memory-safe library is memory-safe for | _everyone_ , even for applications that aren't particularly | careful with their tests. The library author is able to | express their safety requirements through the language, and | all callers have to respect those requirements all the | time. | paavohtl wrote: | It's not that Rust catches these issues like a sanitizer or | linter would, it's that they can't exist by design. The | compiler is aware of potential type-, lifetime- and | ownership safety issues, and you don't need to run any code | to figure out if your code is memory safe or not; static | analysis is enough. | | Also, even if Rust only caught by itself what Clang and | half a dozen analyzers would catch, you also get a really | nice language as a bonus. There is much more to Rust than | just safety. | benreesman wrote: | So, I'm not really sure how the distinction between | `rustc` and the government-issue `clang` chain really | matters unless one is a lot faster, and they're both | really slow. | | The by-design thing I get for like something with a port | open, but as a default? It's been a few months but the | last time I was trying to hack a Rust project printing | stuff out hit the borrow checker. I can live without | that. | paavohtl wrote: | You've mentioned the printing issue a couple of times in | this thread, and it sounds pretty weird to me as a | someone who has used Rust for a couple of years. Without | knowing the specifics I can only assure you that if you | know the basics of the language, you'll have no issues | printing whatever you want whenever you want. | | Rust is admittedly a language you can't just jump into | without spending some time learning the fundamentals. If | you try to do anything non-trivial without grokking the | basics of ownership and borrowing you are bound to run | into frustrating issues. | db48x wrote: | He probably tried to do dbg!(foo), which takes foo by | ownership and consumes it. Doing dbg!(&foo) will | accomplish the same thing (printing foo along with the | line number and file name), but because it only uses a | reference to the foo, it won't consume the original. This | is perfectly sound and logical once you know the | explanation, but it is pretty weird to a newcomer. | benreesman wrote: | The project was alacrity and my checkout is on a laptop I | don't have on hand at the moment so unfortunately I can't | gist my patch, but I'll have that box tomorrow and I'll | try to remember to do so. I was attempting to use | whatever all their other logging was using. '&' was the | first thing I tried because I've done the tutorials and | then some, but after that got the borrow checker arguing | about my new line of code as well as two others I didn't | touch, I rotated through all the other sigils brute | force, IIRC it was a combination of commenting out | another line and ' _' that got me a log line. Now maybe | that's on alacrity because maybe they do ownership in a | weird way. But even in Haskell, even as a novice on | stackoverflow, 'unsafePerformIO' will get you a line | printed to STDERR, and it's not 50-80s to build 50-ish | kloc every time. | | I like learning programming languages, I like working on | compilers, this stuff is basically my only hobby. | | But as the TF2 people are learning the hard way, if a | complete novice can't get a log line in without | understanding a bunch of novel context, PyTorch is taking | your SO out to dinner pretty soon. | | Now if Rust could build a million lines of code in 1-5s | like it should be able to (and like it seems that e.g. | Jai can) I wouldn't care. I needed to get stuff done in | C++ bad enough that I learned template metaprogramming, | which I wouldn't wish on an enemy. If Rust made my builds | 50-100x faster in low optimization settings, I'd write it | on clay tablets. | | But what I actually get is a comparably slow build, a | comparably "!$&@_>>>>"-heavy syntax, yet another failure | to do macros that impress a Lisper in a curly brace | language, and now the static analyzer's usually good | advice is mandatory and can't be turned off to do a quick | experiment. | | I always have to watch my cynicism because I'm a bit | autistic and I offend way more often than I mean to, but | if I've got 1MM lines of C++, I've got a pain in the ass | on my hands. If I start porting any meaningful part of | that over to Rust, now I've got two giant pains in the | ass. | paavohtl wrote: | Initial builds are indeed quite slow, especially when a | project has a lot of dependencies. However, in most cases | further builds are not that bad. I have never built | Alacritty from source so I don't really know if there are | any specific issues with it, but from my experience | incremental builds take just a few seconds in medium | sized projects. | | Additionally during development a full build is usually | not necessary all the time; oftentimes it's just enough | to check the code for errors with `cargo check`, which is | usually significantly quicker than building a full debug | binary. | | Edit: | | I tested building Alacritty from source on my computer | (Ryzen 5600X, Windows 10). The initial build (consisting | of 130 crates) took 34 seconds. Building again after | adding a single print statement took 6 seconds, and | `cargo check` took about half at 3 seconds. It's not | ideal by any means, but IMO it's not also unreasonable | for 25K lines of code, plus those ~130 dependencies. | beltsazar wrote: | For one thing, it's the only non-research programming language | that can guarantee at compile time your program free from data | races. | matklad wrote: | It seems like in your case the easiest path might be to learn | how the language works and see it for yourself. Otherwise, I | think these posts are roughly in the genre you want: | | * http://dtrace.org/blogs/bmc/2018/09/18/falling-in-love- | with-... | | * http://dtrace.org/blogs/bmc/2020/10/11/rust-after-the- | honeym... | | * https://gregoryszorc.com/blog/2021/04/13/rust-is-for- | profess... | stevenhuang wrote: | That last article is a great comprehensive summary of Rust's | features. Will be passing that around, thanks. | benreesman wrote: | Bookmarked. Thank you! | oconnor663 wrote: | Here's one of my go-to examples. This is spawning 10 threads, | each of which appends some characters to a shared string. First | the C++ version: shared_ptr> my_pair = make_shared>(); vector thread_handles; for | (int i = 0; i < 10; i++) { thread thread_handle([=] { | lock_guard guard(my_pair->first); | my_pair->second += "some characters"; }); | thread_handles.push_back(std::move(thread_handle)); } | for (auto &thread_handle : thread_handles) { | thread_handle.join(); } | | And now the exact same code in Rust: let | my_string: Arc> = | Arc::new(Mutex::new(String::new())); let mut | thread_handles = Vec::new(); for _ in 0..10 { | let arc_clone = my_string.clone(); let | thread_handle = thread::spawn(move || { let mut | guard: MutexGuard = | arc_clone.lock().unwrap(); guard.push_str("some | characters"); }); | thread_handles.push(thread_handle); } for | thread_handle in thread_handles { | thread_handle.join().unwrap(); } | | ===== Highlighting some visible differences between these two | examples ===== | | - C++ doesn't actually need shared_ptr here. It would be happy | to access a string and mutex on the caller's stack, and the | only reason I didn't do it that way here was to keep the | behavior as close as possible to the Rust code. However, Rust | requires the reference-counted smart pointer (Arc = "atomic | reference counted"), to avoid holding direct references to | objects on the callers stack that might hypothetically not live | long enough. Basically, Rust doesn't understand that the join | loop makes it safe.* If you want to access caller stack | variables from threads in Rust, there are ways to do it (see | Rayon or Crossbeam), but the basic std::thread::spawn won't let | you. | | - Mutex in Rust is a container of one element, kind of like | shared_ptr and Arc are. The MutexGuard that you get from it is | also a smart pointer to the contained element. You dereference | it to access the String on the inside, here implicitly with the | `.` operator. This is a big part of Rust's safety story: it's | syntactically impossible to access the String without locking. | | - Because C++ copy constructors are implicit, moving `my_pair` | into the closure invokes its copy constructor and bumps its | reference count. But the equivalent syntax in Rust does a | bitwise move, which doesn't run any type-specific code. So to | get the effect of a type-specific copy constructor in Rust we | use the explicit `.clone()` method, which bumps the reference | count of our Arc. (If we had forgotten this and tried to move | the original, it would be a compiler error, because Rust moves | are destructive, and the compiler knows the for-loop will move | more than once.) | | ===== My thoughts about this ===== | | - This Rust code is genuinely difficult for beginners to write, | no doubt about it. You really have to familiarize yourself with | all the relevant library types before you can get threading | code to compile. | | - That said, there are _so many_ mistakes we could make in | either case, and _all_ of them are compiler errors in Rust. For | example we could forget to use a lock entirely, initialize our | lock guard inappropriatey, or accidentally take a shared /read | lock when we meant to take a unique/write lock. Or perhaps(*), | we might throw/panic before completing all the joins. Those | mistakes will compile in C++ and fail TSan (or abort) at | runtime, but they won't compile in Rust. | | - I think the scariest mistake here might be accidentally | keeping a pointer to the shared string past the point where you | unlock. For example, maybe we pass the string to some method | that implicitly takes a string_view of it, and stashes that | view for later. Rust catches this too! It knows that references | to the string _borrow the MutexGuard_ , and it will not let | them live past the point where the MutexGuard is destructed. | | - I think it's interesting to ask why Mutex isn't a container | in other languages. (Or at least, in other languages that | support generic containers.) I think the previous bullet is the | answer. It's nice to get a syntactic guarantee that you've | locked the Mutex before you touch its contents, but when | there's nothing stopping you from keeping a reference _too | long_ , you can't actually make the strong guarantees that you | want to at compile time. | jkarneges wrote: | You basically describe me. I've been doing C/C++ for like 20 | years and usually for the right reasons (game dev, voip, | embedded, desktop apps, proxy servers). When C++ is not needed, | I choose Python. | | I am super impressed by Rust. As of about a year ago I try to | reach for it in any situation I would have otherwise reached | for C++. | | What stands out to me more than any particular feature is how | competent the Rust creators are in designing for performance. | The performance of C++ almost feels like an accident, because | it was designed when computers were slower, and because for | awhile it was the only game in town (even sloppy C++ often | performs better than high-level languages). With Rust, | performance is an ethos. It is very apparent from the language | design and standard library design, that these people know what | they are doing. | sanderjd wrote: | Personally, I became interested in Rust through early writing | about Servo, a project driven by people who were frustrated by | writing a lot of C++ out of necessity (in the Firefox | codebase), which reminds me of what you're looking for here. | Maybe you can dig up some of that writing, though it may be | pretty out of date now. | simias wrote: | When my Rust code successfully compiles, it usually works. | Frankly in terms of user experience it's selling point #1. I | could talk about the borrow checker, trait-based generics, | native build system and dependency management, performance, | relative ease of writing multithreaded code etc... But thinking | about it the thing I like the most about coding in Rust is how | hard I can lean onto the compiler and type system to spot | mistakes and let me know about them. | | C++ is not even close in that respect. Duck typed generics, | lack of borrow checker, lack of sync/send markers for | multithreaded code, easy-to-abuse overloading (that's even | abused in the standard library, so there's no escaping it), | legacy cruft that springs up in unexpected places, non hygienic | macros, exceptions and frankly I could go on for quite a while. | | I think for some of us Rust is effectively the promised land. I | learned some Lisp and Haskell but I could never really get into | them seriously because despite some cool language features I | just don't like paying the performance costs of a heavy | runtime, dynamic typing and garbage collection. So instead it | was C and C++ for better or worse. But then Rust came out and | said "hey, how about having your cake and eating it too?" and I | never looked back. | | But of course many people don't share the same objectives. If | Haskell and Common Lisp are what you consider to be your | baseline I can definitely see how Rust's very strict type | system would be seen as an annoyance, especially if you don't | mind paying some runtime performance cost for the sake of | simplicity and speed of development. | benreesman wrote: | You seem like someone who could write the blog post I'm | looking for. | simias wrote: | I appreciate the encouragement! Maybe I will, but first I | need to write a custom blogging system. In Rust of course. | secondcoming wrote: | > When my Rust code successfully compiles, it usually works. | | I think it was on a previous HN story where Rust programmers | claimed that to get around borrow checker errors they just | clone things. Now, the code may compile and technically be | correct, but it hardly exemplifies the attention to detail | normally required by a systems engineer. | simias wrote: | I admit that I sometimes do this myself, but it's usually a | last resort when I deem that a more elegant solution would | probably end up being a lot more complicated and probably | not a whole lot more efficient since the cloned data is | only a few bytes. I use Rust on embedded devices with weak | CPUs and only a hundred megs of RAM so I tend to be careful | with cloning. Dealing with a lot of cloned data is also a | good recipe to end up with inconsistent internal state. | | It may be a bit of a No True Scotsman but in my experience | hanging in Rust IRC channels and similar places, the devs | who tend to clone and slap `Rc>` all over the | place tend to be those who come from garbage collected | languages, simply because they're really not used to | thinking about ownership. | | People who come from C, C++ and other non-GC languages are | effectively already used to dealing with ownership and | lifetimes, it's just that in those languages it's up to the | developer to keep track of them. In terms of overall | architecture I don't find that I write Rust much | differently from C++, I use RAII most of the time and when | that doesn't work out I'll carefully chose between cloning | or some smart pointer/container. | | IMO Reference counting and cloning should be the exception, | not the rule, and I'd go as far as saying that if you find | yourself cloning and boxing data all over the place in your | Rust application you're either working on a very atypical | application or you're doing it somewhat wrong. | stouset wrote: | As a Rust proponent I'd agree with this take 100%. | | There are a lot of programmers who approach new languages | by trying to write in the style of their previous | language. That's not really optimal, but it can get you | up and running quickly producing passable results for a | wide variety of (previous, next) language pairs. | | Rust is not one of these languages. If you don't try to | "get" Rust and continue working across the grain, you're | going to have a rough time of things. Rust brings along | an entirely new concept of explicit ownership that's | implicit in other languages, and if you simply try to | push forward without internalizing those restrictions | (and understanding their implications on how to structure | your programs), the Rust compiler will never stop pushing | back against you. | | While there are legitimate reasons to need | Rc>, to Box things, and to clone(), the vast | majority of Rust code doesn't need to fall back on them. | If you find yourself instinctively and repeatedly | reaching for these as a quick workaround for borrow | checker complaints, it's going to be much better in both | the long and medium term to try and understand what the | compiler is trying to tell you about your overall | approach to design. | | This is, I think, the biggest area where Rust evangelists | can make some big strides. There are a lot of program | designs that compile just fine in other languages but | that Rust rejects (or pushes back against) for Very Good | Reasons. And getting new developers over that hurdle | where they understand how to internalize enough about | ownership upfront that they can come up with approaches | that work with the grain is a hard problem, and I'm not | sure how to solve it. There's a moment where something | clicks and Rust goes from being a fight against the | borrow checker to something where your designs just work | out of the gate and I don't know how to get that click to | happen quicker. | ben-schaaf wrote: | > I don't find that I write Rust much differently from | C++ | | Same here, though I do find myself deviating sometimes. | Specifically there are cases in C++ where I could write | code that makes less copies or handles data more | efficiently but I don't because the approach would be | more error prone. In rust these more risky approaches | simply result in having more compiler errors instead of | crashes or other runtime bugs. | jvanderbot wrote: | Long time C++ user here. | | Precisely right (for me!): It is easier to get to compile | because it is easier for me to express and easier to debug | type / syntax problems. Once compiled, it has a very high | probability of just working. | | I'll add that the exercise of learning rust has distilled the | lessons learned in writing "good" C and C++ into a language | that has few other ways of doing things. It's like someone is | guiding me through good multi-threaded, memory management, | and type design practices in C++ every time I work on a Rust | project. | | This experience absolutely translates back when I'm working | on C++ and C. | | Rust has huge potential it it can keep its libraries | "upstream" of other languages. C is king of providing a one- | implementation-to-many-languages (though API wrapping). If | Rust can accomplish that, it'll be my language of choice. As | much as I love the Unix philosophy of many interacting | programs, the reality is for most systems that dynamic or | static linking is how things interact. | stagger87 wrote: | Presumably you've read enough to know that memory safety and UB | are Rust's biggest selling points? If that hasn't convinced you | already, I'm not sure what would. Just keep using C++. | | (I use C++ and I love it.) | monoideism wrote: | > UB | | Undefined behavior? As in, you can confine it to `unsafe`? | tialaramex wrote: | Unsafe Code isn't allowed to have Undefined Behaviour | either. | | What unsafe does, is it says to the compiler: | | "Hey, I know you're just a dumb machine, and you couldn't | prove this code obeys all the rules that keep Rust safe, | but I am very smart and I promise it actually is safe". | You're also encouraged to explain yourself (to other | humans, not the compiler, this isn't Wuffs) so that anybody | else maintaining the code can see why exactly you believed | this was OK. | | Over the years, some code which was once unsafe in Rust no | longer needed an unsafe block, because Rust's compiler got | smarter, "Oh, I see why this is OK". | | But if your unsafe code has Undefined Behaviour that's | actually a serious bug, it's not what unsafe is for at all. | benreesman wrote: | I have forlorn hopes that Rust will fix build times and I'll | tolerate the abusive relationship with the borrow checker if | it can move the needle on that. | firethief wrote: | Fighting with the borrow checker is just a phase you have | to get through, it will click. The need to write anything | that can't be done without unsafety is rare (probably never | unless you are writing a data structure library). | k_bx wrote: | My favourite three compared to years of Haskell experience are: | | - runtime predictability. Our async high-load programs are | already complex, and having a much more predictable runtime (no | GC) makes things feel much more straightforward | | - lack of bracket need. You get destructors from your types! No | more async-exception or other accidental leaks | | - no exceptions. The "?" syntax is truly genius, Holy Grail of | Haskell error handling coming to life | grok22 wrote: | The column for the Rust language on this page | (https://scattered-thoughts.net/writing/how-safe-is-zig/) with | the rows of "compile time" for various memory issues is what is | selling me on Rust that I am spending time with it hoping to | sacrifice some difficult current learning time and some future | tedious development and compile time to save on painful debug | time for some kinds of issues. The problem is that the pay-off | is at the end :-). | da39a3ee wrote: | > 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, | | The O'Reilly book by Jim Blandy and Jason Orendorff. It is a | really fantastic book on a programming language, the best I've | ever read. The new edition is due this year it seems. | | https://www.oreilly.com/library/view/programming-rust-2nd/97... | benreesman wrote: | Ordered. Thank you! | ahartmetz wrote: | Yeah, I also like it better than the official Rust book. My | favorite programming language book is still K&R's C book, | though. | tialaramex wrote: | K&R is excellent. One of the things that makes it stand out | would be almost irrelevant today, it has an excellent hand- | made index. If you're wondering about, say, arithmetic | conversions, the index gets you straight to the page which | lists exactly what you needed. Wondering when to pick enum | over #define? Again, the index has your back. | | Today online you've got full text search, maybe you find | two, three hits that are irrelevant, but it's so cheap you | barely care. And in a book today the index is probably | auto-generated (hiring somebody to write an index is not a | thing these days) and so it's almost useless with dozens of | irrelevant entries, but hey, like I said, full text search, | so who cares? | | Because C is such a small language _and_ it was essentially | finished when the book was written, they get to do a pretty | complete survey while also teaching you, so you read the | book once, now you understand C pretty well. The Rust book | is much better than C++ books I tried, but because Rust is | still immature there are big sections that are being | rewritten or have already been rewritten, and of course the | whole book can 't be reordered and started over each time, | so overall it's uneven. | | I am reluctant to buy a printed Rust book because of that | immaturity. My (second edition) K&R is still a pretty good | survey of the language. Are there things it doesn't cover? | Yes. But few of them are fundamental, whereas I feel like | if I bought a Rust book today, in five years it's a | historical curiosity like my Stroustrup, except hopefully | better written. I still consult my K&R a few times a year, | I don't even know where the books I own on other languages | (including the long obsolete Stroustrup) are, I might not | have unpacked them after moving years ago. | steveklabnik wrote: | If you take a look at the Rust book from five years ago | and today, it's actually 40 pages slimmer (540 pages for | the first version, 500 for the second) because most of | the examples for some more advanced features Just Work | without needing those features explicitly, and some other | things got simplified. None of that stuff is _wrong_ , | and is still useful at times, but is used less often. | | The core feel of Rust is very much the same today as it | was then. Just things got smoother overall. We'll see in | ten more years. | | (This is on my mind, as we'll eventually be releasing an | updated book for Rust 2021, at some point. Probably early | 2022. It will grow in size again, mostly due to adding | stuff about async/await, which is a major new addition to | the language, but only needed in certain circumstances.) | unrealhoang wrote: | Seconded, this book is so good in demonstrating Rust's | advantages as well as low level programming concepts with its | beautiful visuals. | pizza234 wrote: | I'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 wrote: | Not 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. | runawaybottle wrote: | This language just does not look like much fun at all. | llogiq wrote: | Looks can be deceiving. You'll have to try it to see if you | like it. | | And if not, that's totally OK. | Traster wrote: | Shouldn't the java in the first example explicitly state it | implements the interface? | llogiq wrote: | Oh, right! Thank you for bringing it up, I'll fix this. | yannoninator wrote: | I 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. | PragmaticPulp wrote: | I 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. | smoldesu wrote: | It'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. | dang wrote: | Looks like this is only the second one: 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. | llogiq wrote: | Author 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). ___________________________________________________________________ (page generated 2021-05-15 23:00 UTC)