Things I hate about Rust
blog.yossarian.net
blog.yossarian.net
Yeah, this is a bit of a farce. On the one hand, it seems fair to me to not have this in the standard library - it's not something that needs special cooperation with the compiler or runtime, it's not something universal (what would it do on an embedded microcontroller?), and it doesn't matter if different crates use different implementations. On the other hand, deprecating a standard library function in favour of a crate owned by a random user seems like an unforced error.
There's discussion here:
https://www.reddit.com/r/rust/comments/ga7f56/why_dirs_and_d...
Including the fact that there is an actively maintained alternative:
And a clue to why those repos are archived:
> I was wondering the same thing and contacted the author. He said that some people had "lost their shit" over some of the things he mentioned about rust and that to help them calm down, they will be archived until 2022.
!
It's filesystem access, like the open file and read file functionality, so it needs some runtime support, and all filesystem access would fail on embedded microcontrollers, so I'm not seeing why it shouldn't be in the standard library.
I'd dispute that on the grounds that a microcontroller with a filesytem is likely doing something complicated enough that it lives on a board with at least one other microcontroller that doesn't.
I'll even go as far to say it's more likely that the majority of microcontrollers don't write to mutable storage other than RAM (if they have it at all) once they leave the factory.
Throw an exception or return null?
From my failing memory, MBCStr, CComBStr, BStr, CString, XLString, and then all the unicode versions. Then you're developing in C++, so you also have std::string and std::wstring, oh and u16string, and u32string. Plus char, and wchar.
I agree completely that everything about string typing on Windows is worse, especially once you stray off of the happy path. But the post isn't intended to equivocate against a ecosystem with 40 years of legacy APIs in it; it's to nitpick a new ecosystem that I otherwise love ;)
This blog post [1] may interest you (if you haven't already read it).
[1] https://fasterthanli.me/blog/2020/i-want-off-mr-golangs-wild...
It's the unfortunate case that we have OS environments that are usually, but not always, UTF-8, and there's no clear, reliable indication of whether or not UTF-8 is to be expected.
https://fasterthanli.me/blog/2020/working-with-strings-in-ru...
Yeah, I guess maybe nothing will ever be as bad as strings in Windows with C++.
But rust should't go down this road any further.
There are some things that I'm bitten by as a rust newbie though; strings are definitely one of those, since it's more difficult to reason about strings as they're heap allocated, where as most "normal" types are not, thus to use Strings you have to learn the borrow checker..
Another issue I have is crate versions, and compiling and including _multiple versions_ of the same crate, because that crate is also a dependency on one of the crates I'm using.
Yet, another is crates that only support the nightly compiler, which you're not supposed to use for production, there needs to be some way of delineating the "stable" version of a crate with the "potential future" version of a crate.
Even if you stick to a version and the nightly compiler supercedes then you end up with the same error:
------8<------
Caused by:
process didn't exit successfully: `/Users/dijit/projects/rust/Rocket/target/debug/build/rocket_codegen-6474f79f6391da32/build-script-build` (exit code: 101)
--- stderr
Error: Rocket (codegen) requires a 'dev' or 'nightly' version of rustc.
Installed version: 1.43.1 (2020-05-04)
Minimum required: 1.33.0-nightly (2019-01-13)
------>8------[0]: in POSIX the OS _must_ set $HOME: https://pubs.opengroup.org/onlinepubs/009695399/basedefs/xbd...
$APPDATA on windows[0] or $LOCALAPPDATA
$XDG_CONFIG_HOME on linux[1], which is where you're getting the ~/.config from.
For MacOS, it's a mess[2], you should probably use ~/Library/Application Support/script_name/ for most things though.
[0]: https://ss64.com/nt/syntax-variables.html
[1]: https://wiki.archlinux.org/index.php/XDG_Base_Directory
[2]: https://developer.apple.com/library/archive/documentation/Fi...
XDG_CONFIG_HOME is ~/.config if not otherwise specified.
I don't think them being heap allocated is the issue. I think the issue you're running into is that String doesn't implement the Copy trait, meaning they're not automatically copied for you. Which is what you want, because you don't want to be implicitly copying large strings without realizing it. But yes, it does mean you have to learn the borrow checker.
That said I know a lot of folks really dig it and think it's a breath of fresh air; more power to em'.
But I find that Go does not spark joy for me. ('.Dial'?!, first character case denotes public/private D:).
I guess that it is very subjective.
Python: https://wiki.python.org/moin/TcpCommunication#CA-b22bc303d6d...
Ruby: https://ruby-doc.org/stdlib-2.5.1/libdoc/socket/rdoc/TCPSock...
Rust: https://doc.rust-lang.org/std/net/struct.TcpStream.html
Julia: https://docs.julialang.org/en/v1/manual/networking-and-strea...
C++: https://github.com/KMakowsky/Socket.cpp/blob/master/Socket.c...
Win32 C: https://docs.microsoft.com/en-us/windows/win32/api/Winsock2/...
Linux C: http://man7.org/linux/man-pages/man2/connect.2.html
Swift: https://github.com/apple/swift-nio/blob/master/Sources/NIOEc...
Java: https://docs.oracle.com/javase/7/docs/api/java/net/Socket.ht...
Javascript: https://gist.github.com/tedmiston/5935757#file-nodejs-tcp-ex...
Anyway, it's not about being demoralised, it just doesn't "spark joy". :)
It might not seem much (and higher level languages usually abstract away the craziness that UNIX sockets are in C), but that's what the OS still gives you in 2020...
More information here: relevant man entry: https://www.unix.com/man-page/plan9/2/dial/
It specifically mentions making a call.
The example even dials "kremvax". Usenet over AX.25 from the 80s. Pretty sure that is _actual_ dialling.
But, I'm curious as to why Go's "Dial" is more powerful than a standard connection in any other language.
The example with calling kremvax over datakit specifically goes to show-off that all that specifies datakit usage is the "dk" string - nothing else.
Similarly with XTI (and other OSI-oriented network APIs), what you are telling OS is "I want to have a stream oriented connection with graceful close, to service Y on host X", and you don't have to care at all whether it will be IPv4, IPv6, TUBA, CLNS over Ethernet, or direct serial modem exchange using HDLC.
Go doesn't have all of that flexibility because it works from userspace, but it reuses the "service, not implementation detail" approach and lets you concentrate on human-readable domain names and service names instead of providing a maze of "hardcoded IP" issues.
The reason no standard interface has replaced getaddrinfo + socket + connect is probably because it's just barely simple enough for common usage in C, and C was never the language you turned to when you wanted to write something short and sweet--that's why Unix environments have always hosted a myriad of other languages. If initializing a network connection were as complex as in Plan 9, doubtless Unix would have provided a more succinct libc interface for the common case
The BSD Sockets API is also close to the simplest possible interface for supporting all the various address and socket option combinations that are possible. (The kernel provides mechanism, not policy.) So even if POSIX, Linux, or whatever included a better standard interface for initializing a connection, it would have to be in addition to the BSD Sockets API (or equivalent).
They are literally a low-level API that happened to be part of IPv4-only stack because DoD had short deadline to get IPv4 ported to VAX and other new Unix machines.
And OS should provide a policy when it comes to networking, otherwise you end up with never ending story of working around other's software to implement them.
Also a fairly good development experience (compared to C++)
Like, I find it extremely pleasing to write any kind of prototype in Rust and make things work. I guess once you have borrow checker embedded in your head and your newly written code compiles without errors most of the time, it becomes more pleasing, but that's not even the main point. There's something about it that's "just right" for me, not anything in particular but a multitude of various things.
These days I'm working again with Rust to add a new feature to a project after not touching it for months, and that feeling of "oh dear god this is awful" that I felt when I was starting out with the language was back again. However, once everything clicked in, with the compiler telling me exactly what was wrong with the code, and the project compiling which meant that I could be damn sure that the code I wrote would work with little to no risk of breaking in unexpected ways... that sure did spark joy.
Just my experience.
I've never understood this. Why continue taking "awful" puffs? Why not just use/ingest something else that doesn't feel awful?
And yes, I get that languages are extremely subjective and some folks find it joyful not awful. Good for them. I'm glad they found happy. For everyone else though...
It's crazy, because this is that part I love the most. Both Rust and Elm just blow my mind every time the compilers not only throw up something I never would've noticed, but even tell me how to fix it! It's crazy to think how far we've come, and it still makes me giddy with excitement every time I see it.
I'm primarily a C# dev, which normally has a pretty happy compiler.
But I've started playing with Websharper (Translates C#/F# code to JS.) One nice thing about the library is that once you understand the basics of composition, your JS -will- work as long as the compiler doesn't barf on what you did.
But, it has the drawback of seeing more errors from the compiler itself. While at first it was frustrating, I realized that the time I spent dealing with the compiler errors was still less time than I would have spent troubleshooting my own handwritten JS.
The package manager is a joy to work with and supports multiple versions of tools.
The edition mechanism allows the language not to be dragged down by old cruft.
The proc macros make it possible to have insanely reusable libraries, like serde.
The normal macros are part of the AST so you don't make stupid mistakes that are hard to trace.
Rust is just so good. But I think it's only obvious if you've worked with C++. Most of this doesn't seem special coming from C#. Except the performance that is possible.
The borrow checker might cost you a few minutes, but it can save you days or even weeks of debugging, and maybe it will save lives some day when Rust makes it to safety critical applications.
One thing that did bother me for a long time was RLS auto complete, which was just really slow and useless. This has been fixed with the advent of rust analyzer.
However, this sense is reducing every-time I see an example of some idiomatic Rust code. I appreciate that they've addressed a lot of C++ pain points.
1. Traits are awesome. (Simon Brand: How Rust gets polymorphism right - https://www.youtube.com/watch?v=VSlBhAOLtFA)
2. The lazy pipeline styled programming is idiomatic and produces optimal assembly. (Ranges in C++20 )
3. Extremely powerful enums. Straight out of functional langs.
These are only a few of the things top of my mind but it makes me appreciate Rust more and more.
If I was working on something where the best alternative is C++, I'd probably feel different. Right tool for the job and all of that.
I hope it's not the tone, but rather how nitpicky the language feels, but the target is for most of the seemingly pedantic or arbitrary restrictions to give you guidance on what needs to be done instead.
Given the relatively sparse nature of the standard library, and the cost of making mistakes (std::sync::mpsc, anyone?!) I can see why this is omitted. IMO it’s a fair criticism that pulling in lots of crates is less than ideal, though.
Can anyone expand on this? I'm not well versed in Rust lore and have just used that module in a recent learning project.
with a link to a comment on GitHub: https://github.com/rust-lang/rust/pull/42397#issuecomment-31...
As to the standard library, I've actually started to think it's too big. I know this may sound like heresy for someone coming from a language with a big standard library. However, I think it should do whatever needs compiler support and have some traits for easier interop and that's about it. EDIT: Perhaps also some functions to help handle the more fiddly UB risks.
Rust has a great, easy to use ecosystem. More needs to be done to point out the trusted and well tested crates and to point people in that direction. The standard library itself is much harder to contribute to and has stability guarantees which can potentially make mistakes costly in the long term.
str is to String
as &[] is to Vec
as Path is to PathBuf
... and the names should reflect that.
Names are hard.
&Vec<T> is "a pointer to a triple of a pointer, a length, and a capacity."
Example: `std::env::var(“SOME_PARAM”).unwrap_or_else(|_| “localhost”.to_string())`
It just seems odd to me, something tells me there is a better way to do this but I couldn’t find it.
Yep! My understanding is that `AsRef<str>` is the right way to do this automatic conversion but I learned that by reading library code, not from the standard documentation. It'd be awfully nice if the compiler could do those sorts of conversions automatically; injecting `AsRef` everywhere adds a lot of visual clutter.
I agree!
`fn foo<T: AsRef<str>>(s: T)`
This is so difficult to read, one has to jump back and forth with eyes to make any sense of these. Why couldn't there be easier syntax for "AsReffing"? E.g.
`fn foo(s: @str)`
I invented @-sign there.
``` fn foo(s: impl AsRef<str>) ```
(I'm recalling the lunch conversations from when I sat in the room with all the Rust interns).
~T was Box<T>.
However, one issue here was that they didn't compose in the same way as types today; ~str existed, for example, but was not the exact same as String in terms of memory layout. Conceptually they're the same thing though.
So this is fine:
let host: &str = "localhost";
Because a &str is just a read-only pointer to some characters [1], and you're just setting host to be a pointer to those characters in the read-only section.But this is not:
let host: String = "localhost";
Because a String is not just a pointer, it's a pointer which implies ownership of an allocation on the heap [1]. That means you can modify the contents of a String, and when the String is dropped, that allocation will be freed. You can't just set up a String using a pointer into the read-only section. You need to make an allocation, copy the characters into it, and use that. Which is what str::to_string() does.[1] And a length, but we can ignore that for now.
I think the nuance is between "makes sense" and "fits into the mental model Rust otherwise encourages," i.e. thinking about `T` and `&T` as the fundamental building blocks for ownership semantics. In that context, being unable to directly instantiate a `str` (or having `&String` be a thing that you can occasionally produce when you mean `&str) is deeply confusing to newcomers.
The surprising thing is, I find rust an incredibly effective language at sparing me from worrying about trivialities. Once you learn the slight differences in what all those things mean, you can be pretty direct. Overall I find rust one of the harder languages to write, but one of the easiest to read, and reading code is generally where you'll spend more time.
This non-behavioral stuff matters. Almost literally no one who comes to rust understands the string types with any intuition initially. And strings are really important!
Much the same thing can be said about the evolution of other areas of rust syntax (macros and attributes come immediately to mind). The final result is a collection of syntactic soup with (mostly) well-defined and internally consistent behavior, but with a syntactic expression that bears the archaeological scars of its evolution.
That is not the case.
https://docs.google.com/presentation/d/1q-c7UAyrUlM-eZyTo1pd...
Although it could really do with some more expository text!
Declaring/initializing a medium sized array of structs, is simply too verbose if you have meaningful structure names. What is a few lines in C explodes into pages in rust.
Type inference on declarations but, not function parameters, WAT!
The scoped constructor syntax is designed to miss the concept of RAII.
Array's by themselves are nearly useless, might have just made Vec<> the default array type.
Variables end up being &mut even when its not necessary because the compiler forces the attribute to be carried along in a number of cases when its not rightfully needed.
As I've complained about before, many of the 3rd party cargo libraries need additional traits before they can be mixed with threads/mutexes unless your willing to use unsafe.
Then there are all the bad style choices, starting with the javascript like nested calling (aka obj1.call().call2().call3().call4().call5();) chains spread over whole pages of code. Mismatch braces (yah I know this one is everywhere in C/javascript/java too, doesn't make it right). Trait declarations that are scattered everywhere rather than being centralized like a C++ class.
This is a hard restriction in place on purpose. The compiler could perform inference for the input and output types as part of the language but that means that the actual low level signature of your function could change by changing either the implementation or the callers. This is a compromise we must have to avoid surprises in real programs that have public APIs. On the flip side the compiler doesn't have this restriction for inferring the return type. If you write a function signature `fn foo() -> _` with a body that can resolve to an unambiguous type, the compiler will give you a structured suggestion for the correct type. This way you get the benefit of inference when developing and the benefit of type safety and unambiguous documentation of your code at the cost of mild inconvenience.
> Array's by themselves are nearly useless, might have just made Vec<> the default array type.
I'm not sure what the actionable recommendation is here. Is it to have made [] the syntax for Vec? If so, that would have made Vec special from the language's point of view, when it doesn't need to. Today you can reimplement all of Vec in your own Rust code, but you can't for arrays, because the compiler and language need specific memory layout information about them.
> Mismatch braces
Could you expand? I'm intrigued what you mean by this.
> Trait declarations that are scattered everywhere rather than being centralized like a C++ class.
This enables very flexible design and composition of behavior. It feels very weird when coming from an OOP background, but I've come to prefer it after a while.
What is verbose about declaring arrays of structs?
Arrays are fundamentally different than vectors, the same is true in Rust as in C++.
Types are only inferred for variable declaration, that works for closures too. You can't have an inferred type in a struct declaration either. It doesn't make sense for a function declaration imho, and having worked in languages that do it - I hate it. Horrible practice to drop type args from functions.
I think method chaining is really helpful, particularly the builder pattern. It's also supremely useful for abstracted logic or patterns like iterator combinatory, something that you don't find as cleanly in other systems languages.
Not sure what you mean about traits being scattered? They're interfaces like a pure virtual base class - implementing them is very similar to implementing a base class in C++. Except you can use generic arguments for implementations which makes for some really expressive mechanisms for abstraction.
I think a crate that offers more shell-like convenience functions/macros for scripting would be more powerful than just adding a system()-equivalent to the standard library.
A proper string library should be a Functor, naturally, but it should also be much more like a rope and much less like a list under the covers and in its API.
Want to write a environment variable that lasts the lifetime of the current shell session? No way to do it.
Just like Rust, you can unsafe structures when using libraries in C/C++.
The first case is related to ownership/borrowing as these concepts are fundamental in systems languages.
The second case is a hint that filepaths are more complex than a single string type can represent.
The first is the distinction between "owned" (i.e., heap-allocated) and "borrowed" (types). Arguably, there could have been better language support for this to fix this issue.
The second is the fact that the underlying OS rules for strings and paths do not conform to any sane string requirements on most systems. If you collapse this into normal strings, then you end up with situations where you can't interact with your OS properly.
My very strong opinion is that the language should impose as little cognitive load as possible. Rust seems like it makes you think a lot about Rust, which means you are not thinking about the problem you are trying to solve. Brain cycles are a finite resource.
I don't want to diss Rust too much though. I see it as a potential future replacement for C/C++ for bare metal stuff where you really do want to hand craft the code for maximal performance or directly interface with hardware. That is a different niche than building higher level stuff, and I don't think anyone has managed to make a language that is good for both (yet?).
Coming from C, you should be thinking about lifetimes and mutability and sharing. If you dont, your program may work but be buggy. Rust forces you to think about that.
Go does the same thing in different ways at different costs. It trades memory/performance so that you don't have to worry about allocations and simply doesnt use standard shared mutability patterns (not exactly, but channels take up most of the same space).
I think people are taking issue with the claims that _Rust_ is imposing the cognitive burden. As I said, these burdens already exist, languages just weren't forcing people to deal with them.
[0]: https://fasterthanli.me/blog/2020/i-want-off-mr-golangs-wild...
The key there is "as possible." What is possible depends on the constraints and the goals. Rust does "impose as little cognitive load as possible" generally, given its goals as a language. You can nitpick about some parts of this, but at least all of the examples in the post have good reasons why they need to be this way, thanks to the goals for the language.
Rust and Go have different goals, and so "as possible" is something that means different things for each language.
On the subjective side:
> Rust seems like it makes you think a lot about Rust, which means you are not thinking about the problem you are trying to solve. Brain cycles are a finite resource.
This is true at the start, but once you get over the hump, you don't really spend any time thinking about this. The strictness is what lets you not need to care as much: the compiler will check your work, so you don't have to think about things very hard. It takes a while to get there though.