This is subjective, but there are at least a couple big counterexamples. Non-lexical lifetimes are a huge benefit for newcomers. I've gone through my old projects and just deleted all the nested blocks that I used to need to satisfy the borrow checker. Everything just works now. Also being able to match on `&T` or `&mut T` without putting the `ref` and `ref mut` keywords in each non-`Copy` binding, is another pretty big win. That's an entire keyword that we no longer need to teach to newcomers.
> Basic crates use so many generics, impl Trait and abstractions over abstractions over abstractions that it's really difficult to follow what concrete types you need to satisfy these.
Again this is subjective, but not following concrete types it the entire point of `impl Trait`. The type of `foo.iter().map(...).filter(...).step(...).take(...)` is a monster. Life is better when we don't have to type it. There are also new capabilities that come along with it: APIs don't have to commit to returning a certain kind of unboxed iterator, and unboxed closures can now be returned.
> async/await don't solve much, as besides textbook examples
See the real code examples here: https://docs.rs/dtolnay/0.0.3/dtolnay/macro._01__await_a_min...
() = foo;
Then rustc will print out an error that includes the type of `foo`.Personally, I use the technique a lot for debugging template errors in the language.
template<typename T> struct TD;
Instantiate this as ‘TD<decltype(foo)>’ to get the full name of the type.
Rust’s does seem more concise.
let x: () = foo.iter().map(...).filter(...).step(...).take(...);
I guess this is some autocorrect error? What did you mean to say?
If you are curious: Gasversorgung would be be "gas supply".
let _ = 5 + foo.iter().bar().quux().asdf();
The compiler will now tell you that <your type> cannot be added to an `i32`. const foo = await callApi(url)
console.log(foo.bar)
Why do we need anything more complex than this? let foo = call_api(url).await;
println!("{}", foo.bar);
Syntatical differences aside, what's complex about it?"async/await doesn't solve much" like your whole post confuses me.
I think right now a lot of rust libraries are "hyper generic" because they're intended to be lower level and to be built on top of. Like hyper, as an example. I've never found it particularly hard to work with generic libraries though.
In Go, you can very easily see all the methods that a type implements. However, you don't see which interfaces it implements because it's implicit.
In Rust, the methods are often hidden away in trait implementations, so it's harder to see what you can actually do.
It seems like this might be fixed if the doc tool generated an index to all the methods, as a reference? Or maybe there already is an easier way that I overlooked.
IDEs should also be autocompleting trait impls.
It took c++ years to blow its complexity budge. Rust, "Hold my beer."
Define "needed" ? Code with async/await is radically simpler compared to without - this is not just basic sugar but really, really important due to rust's borrow semantics There is code I have written that required Rc/RefCell ceremony to get around the borrowchecker that will be completely handled by async/await.
You can read more about this here:
https://aturon.github.io/tech/2018/04/24/async-borrowing/
> performs worse than a basic epoll loop
There are open issues and afaik all of these are solvable problems with known/ theorized solutions.
> is more confusing, and less able to handle complex flow
Sorry, that's absolutely just not the case Even if "confusing" is subjective, you can not tell me that writing complex loops/ control flow is easier in futures than in async/await. Just look at the examples here:
https://docs.rs/dtolnay/0.0.4/dtolnay/macro._01__await_a_min...
People who are puzzled by async/await often fall into this camp.
I'd be willing to bet Rust 2021 will be a great place to start at because it will represent 95% of what the language will become having already been put in place. From that point there will certainly be additions to the language in the forms of libraries and features but the mechanisms of how you write code in general will be solidified.
Until then yea, theres a lot of churn along the way to having a "mostly complete" language, especially when the point of the language is to basically be a kitchen sink of everything.
When async/await stabilises, I think it's going to be less about the language changing and more about the ecosystem. I think there is going to be a lot of development on top of async over the next few years and we're going to learn a lot about the right way to structure programs and deliver useful abstractions. This is going to be particularly true in the embedded space I care the most about.
This is the hidden downside of having a good package manager - knowing how to use the ecosystem is as much of a skill in real world programming as knowing how to use the language. To be clear though - that cost is entirely worth it.
Python, knowing it since version 1.6, I doubt it very much.
Reflection, meta-classes, decorators, comprehensions, slots, operator overloading, multiple inheritance and plenty of other features, some of them with semantics that changed across minor versions.
Python the full language, is at the same capability level as C++.
Many just don't realize it, because it is kind of targeted to teach programming and then people move on.
Lua has a similarly powerful debug hook mechanism. Being able to debug the system from within itself is an amazingly powerful feature.
Side bar, I wasn't aware it was a popular educational tool. I was taught type strong languages, and coming into Python felt like writing pseudocode. Too easy, as the Aussies would say!
If CPython devs would care as much as Common Lisp, Dylan, Scheme, Smalltalk, JavaScript, Julia, it surely wouldn't be as slow.
There are PyPy, TrufflePython and OpenJ9 Python, they all suffer from the stigma of not being CPython.
As a C/C++ programmer who has being following Rust from a distance for sometime, with an intention to learn it later, this is somewhat concerning to hear. I've been meaning to start learning it for some time, however, my current workload doesn't provide enough spare time to start absorbing it.
In addition, it seems to be somewhat hard to find detailed information on how to do low-level things that would be very easy in C. Just as an example, one of our simulations relies on being able to craft raw UDP packets in order to "impersonate" remote basestations in our lab environment.
While this is trivial to do in C or C++, I haven't been able to find a good reference on how to do that with Rust. The lack of accessible documentation means that I'm unlikely to consider writing some of these more security sensitive parts of our system in Rust, even though there may be net benefits to doing so.
One thing that I am working on figuring right now is the proliferation of dependencies. But that's a"nice" problem to have.
> Just as an example, one of our simulations relies on being able to craft raw UDP packets in order to "impersonate" remote basestations in our lab environment.
I would assume you would do it in Rust just like you would in C. Where's the hang up?
Why assume? If you know it can be done, why wouldn't you provide a pointer to an example? I've done my Google searching and can't find one. There are endless examples of this for C online, I'm just stating that with a cursory search I couldn't find one for Rust.
The hang up is clearly at my end, I'm currently time poor, so now is not a good time for me to be learning a new language where I can't find good examples for what I'd use it for.
Admittedly I need to start at "Hello World" with Rust anyway before I get as far as crafting raw UDP packets. The point I'm getting at is if I didn't know how to program in either C or Rust, I'd pick C for this problem right now because there are clear examples. That's all.
Otherwise, I would "assume" that you need to use libc bindings over ffi to use whatever udp APIs you would normally use from C. This is how Rust's standard library implements the higher level udp APIs for example: https://doc.rust-lang.org/std/net/struct.UdpSocket.html
But I used a question mark also because this is not something I've done before, so I can't share an example. I wasn't being adversarial. Even my suggestion above could be wrong. That is, I don't understand the essential difficulty here. Perhaps someone else can chime in.
There is a huge difference between creating a generic UDP endpoint like the code you have shown, and crafting a raw UDP socket, where the source IP address is "spoofed", and UDP checksums generated to ensure that the packet looks like a valid UDP packet so that the intermediate network infrastructure like routers, switches and firewalls will pass the forged packet rather than discarding it.
The latter requires quite a bit of extra code. A good example is here:
https://www.binarytides.com/raw-udp-sockets-c-linux/
and last time I looked (month or so ago?) I could not find an equivalent example for Rust. It may be out there but I was unable to locate it. I'm not asking you to do my homework for me, just stating that learning what I need to know in Rust to do the above would take more time than I have available right now. That's not to say I won't figure it out in the future when I have more time.
> If you don't know how to do it because you don't know Rust, then fine, that's reasonable. (Albeit a strange criticism to lodge from my perspective.)
It's not a criticism of the language; what I'm asking may be very possible, I don't know yet. I just know that finding C examples is trivial, Rust examples not so much, so when I had a need for this kind of code recently, I turned to the language where I know it can be done and I could easily find examples.
Then someone jumped to the conclusion that you were actually saying that Rust doesn’t have enough low level kernel APIs because that would make sense.
Well you could do it in Python I suppose - here's someone mentioning a solution using scapy, although it likely wouldn't work too well for my particular case due to Python performance.
https://stackoverflow.com/questions/51311741/generating-a-sp...
> So people are confused when you name this as a criteria for selecting a language, since it’s a criteria that most likely only C can satisfy
Rust is intended to be a replacement for C and C++, no? Eventually it will need to be able to do things like this if it is to be effective in that role. It's OK in my mind if it is not mature enough to do these kind of tasks yet, or if it can but I can't find a good reference yet. I wanted to use it for this task, but I selected C instead since it was easy to do that way and I didn't have a lot of time. Not holding it against Rust, it's a much younger language.
> Then someone jumped to the conclusion that you were actually saying that Rust doesn’t have enough low level kernel APIs because that would make sense.
I am almost completely certain I don't understand this sentence the way it is phrased.
So I think my head would explode if I tried to learn a FP language without being able to have months off work to study. In addition to that, my team would kill me, as it's hard enough to find good C/C++ programmers in my city, let alone something like OCaml. Given that we run emergency services infrastructure, unfortunately we have to use more popular languages to ensure that the business doesn't end up with a lot of code written in languages they can't support.
Regardless, thank you very much for the example. It's nice to see that this can be done in other languages even if I can't understand it. Unfortunately too many years of C/C++ have made it hard for me to grok FP code - the C example I posted up before is much more readable to me.
What's so hard you find about OCaml or FP in general you haven't had to deal with in your day-to-day imperative programming?
> Unfortunately too many years of C/C++
You mean you find OCaml making your head to explode after years of C and C++?
I guess I still find FP very abstract as a concept. I'm starting to like the notion of immutability as a design goal. But then I'm the kind of sicko who enjoys assembly language to some degree, where practically every instruction has a side effect, whether it be assigning a value to a register, or setting a bit in a flag register, or doing I/O whether port mapped or memory mapped.
> You mean you find OCaml making your head to explode after years of C and C++?
Not specifically OCaml, but most FP languages in general. I find the OCaml code posted up a couple of levels to be very hard to understand. But I'm very used to the POSIX interface and honestly programming against much else tends to get me out of my comfort zone a little too much. Shell, Perl and Python I can manage as well because I've been doing them so long, but I don't excel at those, particularly the latter two.
I think the paper referenced in this (https://news.ycombinator.com/item?id=15179188) HN article ("Some were meant for C") captures my mindset very well. Some of us are just a bit broken as programmers.
I also enjoy the ubiquity of C compilers - knowing that I can program in it from anything from a microcontroller to a multicore CPU (even being able to use the same compiler version accross many platforms) is a huge boost.
I'm rather asking about OCaml or SML than about something abstract matters like denotational semantics, system-F or pure lambda calculus.
There are pretty much the same abstract concepts behind imperative programming languages as well: operational semantics, Turing machines, which, I argue, are even more complex.
And if you write OOP, you have to deal with open recursion, covariant/invariant/contravariant subtyping relations, late bindings, polymorphic self type etc etc. There is a book called ``Theory of objects'' by Luca Cardelli (who is guilty for both SML and C++, BTW) if you are interested.
You just don't bother yourself with this theory, and neither should you when programming FP. You don't bother number theory and arithmetic (complex topics) when doing simple addition in your code, right?
> But I'm very used to the POSIX interface and honestly programming against much else tends to get me out of my comfort zone a little too much
Then maybe your starting point should rather be this [1].
(Although this book is rather about system unix programming in OCaml than about language itself. For learning the language its manual should fit pretty well [2], finding a sweet spot between a narrated book and a standard. Other good and more in-depth introductions are [3] and [4]).
There is also a unikernel called mirage os which includes TCP stack and other stuff and gives a good taste of what system programming in OCaml looks like.
Edit:
> But then I'm the kind of sicko who enjoys assembly language to some degree, where practically every instruction has a side effect, whether it be assigning a value to a register, or setting a bit in a flag register, or doing I/O whether port mapped or memory mapped.
FP doesn't disallow side effects, a usual FP practices just encourage to deal with them explicitly. There are low-level FP languages like ATS [5] (though I discourage you to look at it since it's unnecessarily complicated and could demotivate you), F* [6][7] and even Coq to some degree [8] which allow to deal with effects like allocations and state just fine (and prove invariants).
[1] https://ocaml.github.io/ocamlunix/
[2] https://caml.inria.fr/pub/docs/manual-ocaml/
[3] http://dev.realworldocaml.org/
[5] https://www.youtube.com/watch?v=zt0OQb1DBko
[6] https://fstarlang.github.io/lowstar/html/Introduction.html
[7] https://fstarlang.github.io/lowstar/html/LowStar.html#memory...
[8] https://www.microsoft.com/en-us/research/publication/coq-wor...
Documentation: https://docs.rs/pnet/0.22.0/pnet/packet/udp/struct.MutableUd...
In any case, I don't see anything particularly interesting in that C code. It's just shuffling data around and using some C functions, all of which is pretty easy to do in Rust. With that said, I do agree that translating that code would be non-trivial for a Rust beginner and would require getting pretty comfy in the language first. You'd likely also have to redefine any structs that you need in Rust (or use a tool like `bindgen` to generate bindings to, e.g., netinet/udp.h for you). But still, as an experienced Rust programmer, I think that C example would be enough for me to tackle this particular problem. So this might just be a case of being unfamiliar with the language, which is totally fair. There are going to be far less resources for Rust when compared to C.
As per my other message, I apologize if I have offended you, it certainly wasn't my intention, I was just trying to clarify the use of the raw IP API, as opposed to the higher level solution which wouldn't work for our simulator.
Yeah, it would be, but Rust's standard library doesn't provide any way to invoke syscalls. You either need to go through libc's syscall wrapper, or write Assembly. I used the former to get access to faster directory traversal: https://github.com/BurntSushi/walkdir/blob/ec33af7f9b25afefc... (The rest of that code in that module may be interesting to you, since the API for getdents is pretty interesting and very C-like. Buttoning it up behind a safe API that can never be misused took a bit of thinking.)
Besides, Rust's standard library, on Linux at least, currently requires libc anyway. So you're already going to be linking to it.
> I would "assume" that you need to use libc bindings over ffi
I wonder about whether that is even necessary. socket() and sendto() are basically C library wrappers around system calls for the most part. Surely the Rust standard library already has equivalent wrappers, or will someday. There's no real reason I can think of that I'd need to FFI into C unless there is some limitation of the current Rust standard library.
You can use libc::syscall to make syscalls in the normal way, and that's technically ffi. Otherwise, to do raw syscalls, AFAIK you need to write some Assembly to do it. Running an assembler and linking it into a Rust program is pretty straight-forward. It would be nicer if one could use inline assembly (and let the compiler handle it for you), but that hasn't been stabilized yet.
The topic of whether to build a Rust standard library without libc is definitely one that has gotten some attention, and some folks have made some progress on that front, but AFAIK there is no serious ongoing project or effort that attempts this. Some folks definitely desire it though, at least for Linux. (In other platforms, like macOS and Windows, raw syscalls aren't a stable interface, so you kind of need to use the system libraries.)
The basic answer on this is you can specify that structures in rust have the same representation as a comparable C structure. If this isn't good enough for your use case (and it isn't when representing some wire format), then there are additional keywords to more precisely define the packing.
Of course, it's also possible to just have a binary blob of the appropriate size and write the appropriate values at the appropriate offset inside of it.
Generally I've been able to do whatever I need to do in Rust. I also wouldn't discount wrapping some C code at the very lowest layers. This isn't very different than using C code in a C++ project. Yes, it weakens type safety, but at the end of the day we have to get shit done, no?
To the higher level point of whether or not it's worth learning rust: I don't know. How much time do you spend implementing and how much time do you spend fixing? Rust increases the former and decreases the latter.
Rust instead puts all of these complexities in front of you: Deal with it now.
So you have a choice: Deal with it now. Or deal with it later. And Rust has picked the former.
I however do find writing rust to be quite enjoyable.
But I have so far entirely avoided the futures ecosystem and consider it a very good decision. One needs to have a very critical eye on what libraries to use
Rust is simpler to use, and simpler to learn than it ever was.
It has removed need for common pointless annotations (like ref in patterns, extern crate). Ambiguity of static vs dynamic dispatch in traits has been clarified with `dyn` and `impl` keywords.
The const generics feature is going to obsolete the worst-of-the-worst abuses of the type system trying to emulate that feature, and remove gotchas around arrays.
Async/await enables use of borrowed types in async code (freeing users from having to know about the `Pin`/`Unpin` implementation detail). Allows use of regular error handling, instead of requiring users to keep track of error types at each step in the chain of futures. It allows normal control flow instead of arcane wrangling with `Either` and boxing.
You listed a bunch of problems, and at the same complained that Rust has been solving them!
https://drewdevault.com/2019/03/25/Rust-is-not-a-good-C-repl...
Arguably, all dynamically typed languages are also more complex because there are huge classes of programs that are incorrect which simply would not even compile as valid programs in C++.
If complexity is based on how likely a syntactically correct program is also logically correct, then type systems remove a lot of complexity.
There are no programs (including ones that produce runtime failures) in a dynamically-typed language that cannot be duplicated in C++.
There are certainly dynamic-typed programs wthat will fail at runtime where the natural way to attempt to express the same idea in C++ would result in a compile time error, just as there are dynamic-typed programs that will operate correctly that are harder to express in C++ because you either have to do type gymnastics to convince the compiler of their correctness, or evade the compiler by building an interpreter for a dynamic-typed language, to do it in C++. This is a difference in where the complexity in each language lies, not the overall complexity.
Types are not really at the syntax level. I would measure the complexity of a language by the complexity of it's grammar and the size of the standard library/amount of common idioms needed to write most programs in a reasonable way.
Is https://github.com/hyperium/hyper/issues/1137 the issue you are referencing?
Strangely, the OP says async/await won’t help with things but in this case it would be a big help!
A relatively stable 0.2 release will be out this month.
- Does the compiler use escape analysis to determine that the `cursor` variable needs to be heap allocated? If not, how does it know that the worker threads won't outlive the stack frame that owns `cursor`? If so, when does the allocation get freed? And does implicit heap allocation conflict with the goal of being as fast as C?
- How does the compiler know that the `lock` block is the only code that touches `cursor`? Does it still work if the `lock` block is in a subroutine? Does that require whole-program analysis? If two different `lock` blocks touch the same variable, are they implicitly a single `lock`?
Rust source code is now full of annotations, and very hard to read and maintain.
Traits have evolved from a nice feature to something overused everywhere.
Basic crates use so many generics, impl Trait and abstractions over abstractions over abstractions that it's really difficult to follow what concrete types you need to satisfy these.
When one of the oldest and still unanswered issue in the Hyper HTTP library is "how do I read the body?", there is a conceptual problem.
And things got even more complicated with Futures. async/await don't solve much, as besides textbook examples, these are unusable without deep knowledge of how everything works internally. Pin/Unpin makes things even more complicated.
I'm an early Rust adopter and advocate. However, I wouldn't consider it any longer for new projects.
My productivity in Rust has become very low compared to other languages such as Go or even C, that let me easily express what I want to do.
With Rust, 90% of my time is spent trying to understand how to express what I want to do. Such as what types I have, what 3rd party crates expect and how to convert them.