Rust beginner notes & questions

I see people reimplementing identically named but incompatible traits as a failure: dual_num::Zero - Rust

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.

11 Likes