Rust beginner notes & questions


#1

A bit of background: I’m an experienced programmer who’s worked in over 20 languages including some esoteric ones, and I finally sat down to learn Rust and bumped into what I’m sure are common issues.

Unusually, I’m struggling to find good solutions to these, which is a concern because typically picking up each new language has been easier than the last.

Issue #1: How do I access the iterator itself in a for loop? I.e.:

    for x in vec.iter() {
       ...
       // once we reach a certain point, pass the rest as a slice to some other function
       if *x == 123 { foo( the_iter.as_slice() ); }
       ...
    }

Yes, I know, I can manually write out the de-sugared loop, but here’s the thing: the iterator trait is very rich, and the standard library implementations of it often extend it even further. Why hide this functionality in such a common use-case?

Maybe something like this could work, with analogy to the similar if let pattern:

    for x in let i = vec.iter() { ... }

Issue #2: Many refactorings I would expect to work… don’t. I understand the reason why, such as automatic dereferencing working in some cases but not others. The concern I have is that Rust seems to be treading on dangerous ground, where IntelliJ-style tools will forever be highly restricted. For example, I came across many scenarios like the following:

https://play.rust-lang.org/?gist=eef5e6786275dfe8a070e75923072765&version=stable

This is artificially simplified to highlight the problem: x looks like a u8 to beginners, but is a &u8 because iterators return references. For further confusion, the closure body will automatically deref this, hiding the fact that it’s actually a reference. Move the code out and it just breaks. It can get really ugly when you have complex iterators with nested references, e.g.: &&[T], which I guarantee is going to cause far too much confusion to beginner programmers in similar scenarios. Also note the trivial looking refactoring that I’m used to using without issue in every other language.

Issue #3: How do I create a mutable reference to non-mutable things? The documentation is unclear on the more complex corner-cases, which the C++ documentation for comparison covered reasonably well, such as the difference between a “constant pointer to a mutable thing” versus a “mutable pointer to a constant thing”.

Issue #4 What is the idiomatic Rust way of creating the equivalent of many similar classes sharing a large base class? I’ve been trying to convert some Java and C# libraries, and they often re-use the same huge chunk of code with minor overrides (in the literal virtual function sense) in child classes to produce a wide range of functionality while maintaining DRY principles.

It would be nice if Rust enums could have a common part, e.g.:

    enum Person {
        first: String,
        last: String,
        email: String
        
        Contact(),
        Employee { id: String },
        Contractor { company: String, contract: String }
    }

Or if enums could derive from structs, or… something. All of the “roll your own” OO-equivalent solutions I’ve seen in rust were for trivial examples with just 1 or 2 common members. What’s the solution for 20+ common members?


Explanation for difference between `.filter(func)` and `.filter(|x| func(x))`?
Towards a more perfect RustIO
Difficulty of learning rust and further work on the learning curve
Ripgrep - Confessions of a father
Theory of disagreeing (aka To flame or not to flame)
Discussing emerging memory and Rust
The problem of duplication of crates and ensuring re-use
Are Rust crates secure?
Rust compared to C#
(spectacular) failure of abstraction, compile times, and toolboxes
Lack of IDE integration, Rust comparison to modern languages with respect to foundational theory, and possible fixes
Rust-Windows Interoperability
Stream API and types
(Wrong) Direction of rust
Access iterator in for loop; refactorings not working; mutable referance to non-mutable object; sharing large base class
#2
for (i, x) in v.iter().enumerate() {
    if *x == 2 {
       use_slice(&v[i..]);
    }
}

x: &mut u8 is a constant pointer to a mutable u8; mut x: &u8 is a mutable pointer to a constant u8. mut x: &mut u8 is mutable for both.

There’s nothing super easy for this right now. You can put the common fields into a struct and then embed that struct in the enum variants.


Access iterator in for loop; refactorings not working; mutable referance to non-mutable object; sharing large base class
#3

As for issue #2, it’s definitely part of learning Rust. The heavy use of type inference in most Rust code makes things quite confusing for a beginner because things look a bit magical (as you hint at) until you learn what is actually going on. For iterators specifically, you can use the cloned() combinator to return values and not references (so long as the type implements Clone. To make functions abstract over owned or borrowed values, you can make use of AsRef or Borrow traits.

There are some improvements in the pipeline to make it a bit easier to work with scenarios like this: https://github.com/rust-lang/rust-roadmap/issues/17


Difficulty of learning rust and further work on the learning curve
#4

Thank you for the feedback, but for the iterator case I may not have explained what I meant well enough. I’m aware of enumerate(), but that that returns an index, not the iterator.

What I would like is for an ergonomic way to access both the ‘iterated value’ and the ‘iterator’ at the same time without having to manually desugar the for loop.


Access iterator in for loop; refactorings not working; mutable referance to non-mutable object; sharing large base class
#5

This was recently discussed here: Passing a iterator recursively


Difficulty of learning rust and further work on the learning curve
#6

The feedback I’m trying to express is that it doesn’t feel like the typical learning curve. Keep in mind that I took to Haskell like a duck to water, and that’s generally considered a relatively difficult language to learn.

The link you referenced seems to cover at least some of the issues I’ve noted (yay!), but at the same time it feels like it’s making things worse because it’s yet more layers of ad-hoc magic and bandaids layered on top of what to me feels like a shaky foundation.

I’ve observed that successful languages take some core mathematically sound theory and produce a concrete, applied implementation of it. Relational theory became SQL, typed lamba calculus became functional programming, object oriented theory is wildly popular, etc…

I feel like Rust came >this< close to implementing something similarly ‘pure’, for a want of a better word, fell short, and is slowly sinking into the same messy quagmire that makes C++ so repulsive to me now after having used it for over a decade and then abandoned it for vastly more productive languages.

I think part of this is that most of the Rust core development team comes from a C++ background, and to them, Rust is a huge improvement relative to what they’re used to. This is true, but it feels like a step backwards for everyone else used to elegant languages with a more consistent underlying logic.

The most telling aspect is the general disregard for IDE integration in Rust. It’s been, what, 8 years and there still isn’t a first-class integration with any development environment out there! Tab-complete is woeful at best, and refactoring is basically hopeless.

So… lets all assume that deep IDE integration really is a goal. How is this going to happen? Having poked through the source of the standard library, I’ve noted that huge chunks of it is implemented with macros. The language is a littered with automagic landmines, and from what I’m seeing this is just getting worse. The IDEA team, known for their fantastic tooling across a range of languages, is struggling to make Rust work after over a year of effort. For example, the line use nom::* breaks their IDE, making it forget what str is. I don’t even…

So what’s the fix? I feel like automatic deref is already one step too far in the wrong direction. Compile-time-lifetimes are brilliant, but the fact that NLL took this long indicates that there is no sound underlying theory that can be analysed, understood by humans, or refactored by IDEs.

I’m going to keep poking away at some toy Rust problems as a learning exercise, but I just can’t see any utility in it at the moment. There’s too many ‘undefined behaviors’ for it to feel safe, not enough SIMD to make it truly performant, and it’s not productive enough to replace C# or its ilk at the moment…


Lack of IDE integration, Rust comparison to modern languages with respect to foundational theory, and possible fixes
#7

There’s a lot to address in your post :slight_smile: but I’d like to know how many days (or whatever unit of time is appropriate) in are you with Rust?


Lack of IDE integration, Rust comparison to modern languages with respect to foundational theory, and possible fixes
#8

I looked at it very briefly a year ago, just a couple of days of experimentation in the Rust Playground. At the time, I was excited by its use of traits and the heavy reliance on templates, because I was interested in implementing a practical non-commutative geometric algebra for a relativistic rendering engine. Most languages fall flat on their face when presented with abstract mathematics, but at the time Rust had most of the machinery necessary for such a project. More than Julia! More than Mathematica in some respects! For example, it had a way of defining an abstract One and a Zero, which is the core of category theory and practically all of modern mathematics.

Then the Rust team decided to remove these features, which saddened me a lot. A dozen lines of code snipped out basically removed any hope of the core language progressing along a more elegant path, at least for the problem I was trying to solve. Similarly, the conflation of futures with input/output is disappointing, as is the nerfing of streams to bytes only. Not everything is a file or a socket!

Just recently I decided to do the https://adventofcode.com/2017 programming challenges in Rust for fun, purposefully ignoring practicality to push myself to learn various aspects of the ecosystem. I used the nom library instead of manual string processing, itertools, rayon for performance, etc… I spent about a couple of weeks on this during the holiday break.

Generally, I found myself getting stuck on stupid tiny things, which was a bit disappointing. Things that ought to be easy were hard, and even the best IDEs were of little help. I was shocked at the lack of really basic things in the std library, such as iterating over a list as pairs. That’s an external dependency!! Meanwhile, a BTree container is built-in! o_O

Here’s a snippet of my main.rs file:

    mod day14;
    use day14::day14;

    mod day15;
    use day15::day15;

    mod day16;
    use day16::day16;

    mod day17;
    use day17::day17;

    mod day18;
    use day18::day18;

That’s just… sad. I had to do this, because dumping 25 independent programs into one file is annoying, but Rust forces a file-per-mod structure. There were practical aspects to this too, such as IntelliJ tab-complete failing when faced with files larger than a kilobyte or so.

I’m reasonably certain that I came up with decent solutions, having compared my first stumbling attempts at Rust to other people’s solutions. This is my “Day 1” code as a reference:

    fn day1_a(input: &str )
    {
        let digits:Vec<u32> = input.chars() // process one character at a time
            .map( |c| c.to_digit(10).unwrap()) // convert each character to its number equivalent
            .collect(); // put into a temporary list to allow concurrent processing passes over it

        let mut sum: u32 = 0; // variable for the total to compute
        let next_digit = digits.iter().cycle().skip( 1 );
        for (digit,next) in digits.iter().zip( next_digit ) {
            if *digit == *next {
                sum += digit;
            }
        }
        println!("Day 01: {}", sum  );
    }

So I admit that I have not yet spent a lot of time with Rust, but that’s sort-of my point – I think that the normalization of deviance is a serious problem – people get used to the way things are quickly, so the best feedback is often from new users, not the old guard.


Lack of IDE integration, Rust comparison to modern languages with respect to foundational theory, and possible fixes
#9

This is available in an external crate: http://rust-num.github.io/num/num/index.html

Futures are generic and not tied to IO. There’s, eg, a way to spawn arbitrary functions (closures) onto a threadpool that’s not at all tied to IO.

Not sure what you mean here.

https://doc.rust-lang.org/std/primitive.slice.html#method.chunks. There’s also https://doc.rust-lang.org/std/primitive.slice.html#method.windows.

I don’t think so. You can define multiple modules in the same file or use subdirectories.

Completely agree and your feedback is welcomed. My point, however, is that Rust is a complex language with many features. In addition, many people with experience in other languages are too used to thinking in those languages and view Rust through those lenses - that’s an interesting thing to do but isn’t necessarily the best way to learn Rust.


Lack of IDE integration, Rust comparison to modern languages with respect to foundational theory, and possible fixes
#10

Yes, I know, but that’s a total failure. It’s a core trait, as low level as you get, below low-level number types like i32 or f64. Lifting it out of the std library removes most of the utility of that abstraction. So for example, check out what’s going on behind the scenes of iter.sum():

https://doc.rust-lang.org/std/iter/trait.Sum.html#implementors

That’s a spectacular failure of abstraction, especially given that Zero was already in the standard library at one point. The abstract concept of summing a sequence can be very elegantly and succinctly implemented using the Add and Zero traits. If you’re missing either, you’re forced to use macros, because nobody in their right mind would go through and implement Sum by hand for all of those types. This is exactly what the Rust team did, which to me looks absolutely filthy: https://github.com/rust-lang/rust/blob/master/src/libcore/iter/traits.rs#L772 – notice the $zero:expr parameter? That’s the “zero trait” that was removed and had to be hacked back in by hand.

PS: I’m no longer surprised at Rust’s slow compiles times, this type of “bulk impl for dozens of concrete types using macros” is a common pattern. The lines of code might look fine to a human, but the in-memory bloat must be hideous…

Granted, but just about all of the libraries built on top of the Futures crate seem to assume that I/O is the only thing you would want to use Futures for. I may be wrong, this is just my general impression…

I mean take a look at this trait: https://doc.rust-lang.org/std/io/trait.Read.html

The issue I see is that it’s “overly specialised” to u8, and this can’t be undone now because it mixes in a function that does not belong: read_to_string() among a bunch that could all be generalised to support any Copy type, e.g.:

pub trait Read<T=u8> where T : Copy {
    fn read(&mut self, buf: &mut [T]) -> Result<usize>;
    
    // ...
}

If that one function wasn’t there, and was moved to a text-centric stream trait, then the lower-level concept of streaming (“bulk data iteration”) could be more generally applied to all sorts of things. Too late, it’s bytes only, from IO sources only, forever.

That applies to slices only! Not everything is an array. 8)

Generally, I find that Rust’s Iterator trait is great, but it’s missing core functionality that currently has to be provided by external crates, which again I see as a failure to apply abstractions at the correct level.

About half of the itertools crate should be in std. Functions like any() and all() are particularly simple and shouldn’t require an external dependency.

I was trying to do the opposite: define a single mod across multiple files, as allowed in most other languages. Rust prevents this, forcing messy “pub use …” headers all over the place.

A much bigger problem is code-generator tools. Think of partial classes in C#, where a tool generates some fraction of a single logical block of code, and then the developer can ammend it: https://docs.microsoft.com/en-us/dotnet/csharp/programming-guide/classes-and-structs/partial-classes-and-methods

Right now I believe this type of thing is impossible in Rust, unless I’m missing something…

I feel like it’s too sparse, underdeveloped, and not complex enough! 8)

It’s like I’ve been handed a half-empty toolbox, told that it’s a well-stocked workshop despite all evidence to the contrary, and in fact I’m used to working in automated factories with thousands of robots.

It all goes back to my opinion that Rust “looks good” to C/C++ programmers, because they’re used to using a rusty screwdriver to hammer in nails…


(spectacular) failure of abstraction, compile times, and toolboxes
#11

I’m on mobile so this will be somewhat brief :slight_smile:

Rust stdlib is intentionally kept thin - this is a frequent point of debate and contention. This is generally mitigated by the ease of integrating external crates and the type system allowing implementing traits for foreign types. From a user’s perspective, they add a dependency on num and off they go. I don’t see the “total failure” in this regard?

The same can be said for the itertools crate that you mention.

It’s naturally the case that most uses of futures are for IO. But the trait itself is pretty generic - there’s nothing IO about it.

I agree that a read_to_string doesn’t belong there at the conceptual level. I suspect it’s just a convenience for users given how frequent this is.

But this trait is for reading bytes, which is the atom of data, from somewhere. It’s a low level interface. It’s not intended to be generic over some T. You can build that streaming aspect on top, which is essentially what crates like Tokio do.

Lots of things deref to a slice. You mentioned a “list” which I take to be a Vec - that derefs to a slice and so you can use that function on it.

I don’t think you can split mods across files but you can split impl blocks across files for some type. You can also use some of the include macros to have the compiler “copy/paste” content of another file, which can help with codegen.

Suppose One and Zero were in std, and itertools as well. And Read didn’t have the String method. What’s your take on Rust then? :slight_smile: Your comments are a bit all over the place (topic wise) and I can’t quite tell what is preventing you from being productive in Rust.


(spectacular) failure of abstraction, compile times, and toolboxes
#12

For the iterator use

while let Some(i) = iter.next() {}

The for loop is a syntax sugar for simple cases, and it consumes the iterator (calls into_iter()).


Access iterator in for loop; refactorings not working; mutable referance to non-mutable object; sharing large base class
#13

References around closures/iterators/auto-deref can be tricky. It takes a bit of experience to know iter() is by reference and iter().cloned() and into_iter() are the alternatives.


Access iterator in for loop; refactorings not working; mutable referance to non-mutable object; sharing large base class
#14

Rust doesn’t have a concept of immutable memory. All memory can be mutable in principle (that’s why you can always make owned objects mutable). Only references and variable bindings have mutability attached, and temporary immutability of memory is enforced by restricting where and when these references can exist.

The closest type would be &mut &T, but I don’t recall such thing being used in practice. Maybe let mut x: &T is the thing you need?


Access iterator in for loop; refactorings not working; mutable referance to non-mutable object; sharing large base class
#15

I see people reimplementing identically named but incompatible traits as a failure: https://docs.rs/dual_num/0.1.1/dual_num/trait.Zero.html

I know that sounds harsh and nit-picky, but I feel that it’s a sign of things to come. This kind of thing will inevitably get worse as a consequence of stripping low-level abstractions out of std. The missing wheels will be reinvented, and it’s going to be a huge, incompatible mess.

(To clarify: I’m not generally advocating for std to include all-encompassing implementations for things, but I do feel that it needs to authoritatively and centrally define core traits and a decent set of data types, not just thin wrappers around whatever is provided by LLVM.)

Please take a quick look at the Diesel crate’s cargo.toml dependencies.

This is a top-shelf library, but they were forced to depend on piddly little libraries for all sorts of basic stuff that absolutely does not belong outside of std. Numbers. Bit manipulation. GUIDs/UUIDs. Temp directory. Time. Urls.

Are you seriously saying that in 2018 it’s okay for a new programming language just barely out of 1.0 to not include a UUID type!?! Seriously? They’re everywhere: in file formats, protocols, APIs, and of course databases.

You have no idea how many “enterprise” databases I’ve seen with “nvarchar(36)” columns storing the text-representations of GUIDs for one reason, and one reason only: Java didn’t originally include this type in their core library. Oops. Now there’s thousands of databases out there with 74-byte primary keys because of that mistake.

My takeaway point is this: small mistakes or omissions in standard libraries and language design have an enormous multiplying factor in their cost to society. Thousands of developers write millions of applications with billions of users.

A small convenience for the language designer can cost literally billions of dollars down the road.

I’m aware I’m rambling a bit, but that’s in part because I feel like I’ve hit a hundred little brick walls with code that I’m trying to write, and none of it has to do with the supposed “difficult learning curve” of Rust related to the borrow-checker. That, I understood just fine! :grin:

Instead, I got stuck on stupid little things, like basic loop syntax! Rust has a loop keyword, unlike most of its brethren. Okay, that’s a bit odd, everyone else just uses while ( true ) { ... }, but okay, whatever. However, there’s no do-while loop in the language! It took me way too long to realize that this is yet another unique and special Rust-ism, and the idiomatic equivalent is: while { ... body...; cond } {}

Meanwhile, all the loop types just desugar to loop & break anyway, so would it have killed anyone to just include do-while loops like every other language?

Okay, I might be rambling again, so to get back to your question: What’s is my overall take on Rust, and what is stopping me?

I will poke around with it a bit just for fun because it has some interesting concepts, but as-is I don’t see it ever becoming useful enough to warrant me using it for anything in production because it’s headed in the wrong direction and fixing it would require breaking changes. Over time I expect Rust to get worse, not better. The little short-term conveniences like read_to_string are a slow death-sentence in the long run, unfortunately. The image in my mind is Artax slowly sinking into the quagmire.

Automatic dereferencing is just one example in this category. Someone got lazy and decided they didn’t like typing asterixes all over the place and added an ill-thought layer of magic for their own convenience that is now a landmine for even the most trivial manual refactorings. Combined with the over-use of macros, I just don’t see how the language will ever get IDE support equivalent to what Java had 15 years ago, let alone today.

If a gun was pointed to my head and I was forced to pick one thing that I could point to in Rust that I feel is a total failure and stops me using it is its strings. They are by default mutable, a concrete type instead of a trait, and there are way too many variants of it, not even including char arrays and the like that turn up in interop. I feel like this is a catastrophic design mistake. C++ got this wrong and Java and C# got it right by having one immutable string type.

(Note: I fully understand the practical need for separate UTF8 and UCS-16 underlying string representations, and I support Rust’s choice of UTF8 by default.)

I particularly hate Rust’s strings because 99% of the code I would want to write has to interop with Windows, C#, or Java APIs, all of which are OsString or some variant on a [u16] array, which is just too painful to work with because of all the small inconsistencies and missed opportunities for trait-based elegant code.

Take a look at this gem from String.rs:

impl_eq! { String, str }
impl_eq! { String, &'a str }
impl_eq! { Cow<'a, str>, str }
impl_eq! { Cow<'a, str>, &'b str }
impl_eq! { Cow<'a, str>, String }

First of all, that macro is local to that source file despite being completely generic. Clearly, implementing equality comparisons is a concept unique to strings. ಠ_ಠ

Would you care to make a bet as to whether OsString implements the same traits identically to String/str? Care to put money on whether OsString/OsStr interact symmetrically with String/str?

Let me save you the trouble:

let a: OsString = OsString::from( "foo" );
let b: &str = "bar";
if let Some(_) = a.partial_cmp( b ) { /* Compiles fine! */ };
if let Some(_) = b.partial_cmp( a ) { /* LOL, no. */ };

This kind of thing would rate a Wat!? in the infamous Destroy All Software talk.

Imagine for a second that you’re a beginner and trying to compare an OsString to a static constant &str. Your code has a 50:50 chance of compiling depending on which order you use your variables, which would lead you to incorrectly assume that this feature is unavailable in the standard library about half the time.

That’s crazy.


Difficulty of learning rust and further work on the learning curve
The problem of duplication of crates and ensuring re-use
Subtopic on std-lib thick vs thin
(Wrong) Direction of rust
#16

I take this as good feedback. We clearly have a user who is having a terrible experience with the language and is pissed about it. There maybe others having similar experiences but not being vocal about it. It needs serious looking into.


Does Rust have severe shortcomings?
#17

This may very well happen. The upside is that from the multiple implementations a portable and efficient version can be picked (or created with insight gleaned from the existing impls) and put into std. This boils down to: it’s always easier to add to std than remove/change.

As mentioned, the thinness of std is a contentious point - there are valid arguments on both sides. But look at Java - even it, despite a massive stdlib, has large auxiliary external libs that are widely used - the numerous Apache Commons, Guava, J2EE addons and so on.

Thin wrappers over LLVM? C’mon, let’s not exaggerate and detract from your points :slight_smile:

I agree that fairly fundamental APIs need to be available, one way or another. I don’t think it necessarily has to be in std however.

Well at least that’s good! :slight_smile:

There’ve been a couple of times a do-while loop would’ve been useful in my experience but it’s infrequent and a different formulation isn’t a problem.

As for loop, I like it - the alternatives of while(true) or for(;;) in other languages are just noise - you want a loop? Say it explicitly.

I suspect you’re referring to deref coercions rather than auto deref; the latter is completely useful and obviates lots of noise that would ensue otherwise. Deref coercions, however, can appear magical in the beginning, particularly when coupled with heavy type inference use. They’re less magical with some experience, and in fact a useful feature.

As for IDE support, we’ll see. It has already come a long way in just the last couple of years, and more work is happening. Java has had decades of work gone into its IDEs, and it’s a much simpler language to boot. But time will tell.

Both C# and Java have extensive use of StringBuilders, which is your mutable string. Java also has CharBuffer to view byte buffers as characters. Let’s not forget its StringBuffer legacy type either.

A Rust String is not mutable by default - the mutability isn’t tied to String itself; it has dynamic sizing facilities but it doesn’t mean it’s mutable all the time. String slices are great. The OsStr and CString/CStr are an acknowledgement of the different ways different things represent strings. Trying to shoehorn it all into a single type would likely lead to a bug laden path and a conceptual mess.

What trait do you want to see for a string?

Yeah, this is unfortunate. There are obviously ways to make this work (eg str has an AsRef<OsStr> that can then be used for equality). Not sure if it’s an omission that there’s no direct PartialEq or intentional.


Subsubtopic: dereferencing and strings (especially interop)
Subtopic: Loop syntax
#18

This is what I imagine the story to be behind a lot of things that were stripped pre-1.0 like std::num::Zero.

Stabilization affects the language

At some point in time, the feature existed. However, there were things about it that were simply not as nice as it could be:

  • perhaps the best design was still unclear; it needed further experimentation and design iteration.
  • perhaps rust was missing critical language features to make it ergonomic (or these features existed and were buggy/unreliable), like const functions or higher ranked types. Zero appears to have been such a trait; its unstable feature message used to read:

    Unstable: unsure of placement, wants to use associated constants

  • if something was underutilized there could be unknown unknowns; poor design decisions lurking just beneath the surface that would only be discovered after stabilization. (and even with the team practicing their discretion, some of these still managed to slip through, like std::error::Error)

In the rush to 1.0, a decision had to be made:

Are we ready to support this thing in its current form, without breaking changes, for all of eternity? Or should we wait until it is possible to do better?

Some things (like the f64::consts modules) were simply so fundamentally important that something had to be stabilized even if it was a terrible hack! But many things were hidden behind feature flags on the notion that we can have something better.

For a while after the 1.0 release, you couldn’t even sum an iterator on stable!! Things such as this were slowly added back based on how desparately they were needed. There wasn’t a single person using rust who was not losing hair over the inability to sum an iterator, so std::iter::Sum eventually had to be added even despite being a terrible hack.

So that’s my idea of how these features got removed. However, there is also a second chapter to this story.

The language affects the users

After the removal of something from the standard library, a paradigm shift can occur. People can go from thinking

this is basic functionality and it is appalling that I need to use a third-party crate

to beginning to think

that problem is so complicated that it doesn’t even belong in the standard library. There is no design that is good enough to be worthwhile.

Maybe sometimes this really is true, but other times this view may simply by colored by the status quo. In a way, it’s the blub effect, and I believe we are all susceptible to this.

I know I’m going to receive flak for saying this, but:

We are all at risk of adopting an apologist viewpoint on missing features.

…and it is at this point that the absence of something in the standard library may become self-sustaining.

How can this be prevented? I don’t know. Identifying and challenging one’s own cognitive biases is not easy. Sometimes somebody motivated appears and smacks whole the rust community with a harisen, writing an epic blog post to remind us all that yes, there is still hope for having such a feature in the standard library. But in general?

…hm, there I go again. Always seeing the problem, but never the solution…


I had an example of a possible personal case of this phenomenon, but it has gotten really long for this post. Maybe I’ll make a separate thread…


(Wrong) Direction of rust
Subtopic on std-lib thick vs thin
#19

I applaud the poster and responders here. I see this as healthy critical discussion. I don’t interpret Peter bertoks comments as “pissed”; I see it more as intense, which I think is good that a program language should evoke intense feelings.
As someone who took to rust a few years ago, lost traction, and am now coming back to evaluate I very much appreciate discussions like these. When I evaluate a tech for adoption, which has impacts on the success of my business and my livelihood, I want to know what the warts are; downsides, issues; limitations of a language are far more important than features IMO.

Rust, unfortunately it seems, is now stuck with the “difficult” moniker that plagues Haskell; a sad psychology that creates learning anxiety in people before they even start. Studies on math anxiety have shown this is a real and lasting effect, causing measurable stress. Props to the rust community for taking “usability” head on.

As someone new to the community (and from the luxury of my vagabond status) I can report that I have had conversations with colleagues where they shared the sentiment that if both rust and c++ demand a high cognitive tax they’ll just stick with c++. I find this unfortunate but both realistic and pragmatic. And by high cognitive tax I mean things commonly brought up about rust like fighting the borrow checker, type system, etc… , and things like inconsistent APIs (though I’ve got no real examples so maybe there is a large amount of FUD here…?).

I’m curious what are the pragmatic views/uses of rust? Id love to see examples a non-trivial AND pragmatic rust programs side by side with go and c++. Perhaps they are out there and I just need to look. I’d love to see rust advertised as a pragmatic solution to a class of problems. In my experience it would be a great balance to the well-known complexity. People who have never written or seen rust know it’s complex and I’d like to pull out a pragmatic example and show them it’s no worse than nodejs, go, etc… IMO pragmatism trumps other inneficiencies and fringe quirks.

I’m still playing with rust and dedicated to learn as much as I can and I hope to find that it is the right tool to build my next project (fintech payment collections service using optical character recognition reading receipts and account summaries). To be frank, one reason for picking rust ( over go or c++) is the combination of generics, better guarantees for data races, memory, etc…, and a cleaner API and ecosystem (not really an issue with go).
I have a high tolerance for learning new things and am not afraid to pay people to do the same, if it means landing on superior tech. Part of superior tech is productivity, which I define relative to project and cost of failure. I’d rather spend an extra month(s) building something if that means it will require less maintenance, bugs, and errors over the course of time. I see rust as being able to provide that kind of productivity (versus deliver fast and spend months debugging). But I get concerned when people I know, who are polyglots and not averse to new things, eliminate rust due to complexity… And I can’t help but wonder if that complexity is related to Peter bertoks general issues.

Thanks for all the work you do. I hope I’m not coming off too negative or as trying to create unnecessary anxiety or as telling experts how to do their job.


Setting an example: Rust code to learn from
Difficulty of learning rust and further work on the learning curve
#20

I’m not sure if this counts as a “pragmatic” program, but one of the things that won me over to total Rust fanboy status was when, only a few weeks after reading The Book for the first time, I looked at the implementation of std::arc::Arcand I could understand it. Easily.

This is in contrast to my complete inability to read just about anything in a typical C++ std implementation, despite multiple years of experience being paid to write C++ code and reading plenty of books on C++ obscurities. I know by heart how a typical std::shared_ptr is laid out in memory, but the actual code of any non-toy implementation remains total gibberish.

This is unfortunate. However, the good news is that it’s not a universal view. There are also plenty of anecdotes from people with a background in scripting languages that found Rust “made systems programming accessible” for them.

You may have seen the quote (does it qualify as a meme yet?) that “Rust is difficult because writing correct code is difficult”. I don’t like to say it too often because it risks coming off as shutting down legitimate critical discussion, but there’s also a lot of truth to it. Most of the things that are hard in both Rust and C++ are part of the essential complexity of systems programming, like understanding ownership, stack vs heap memory, race conditions (as opposed to data races), etc. So “the sentiment that if both rust and c++ demand a high cognitive tax” is technically correct… but it also ignores the many, MANY sources of accidental complexity that C++ mostly inadvertently created and Rust, with the benefit of hindsight, successfully avoids or actively defends against.


Difficulty of learning rust and further work on the learning curve