| 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)