Rust 1.37.0
blog.rust-lang.org
blog.rust-lang.org
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.
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?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
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`?
"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.
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.
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.
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.
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.
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.
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!
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.
Documentation: https://docs.rs/pnet/0.22.0/pnet/packet/udp/struct.MutableUd...
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...
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.
https://drewdevault.com/2019/03/25/Rust-is-not-a-good-C-repl...
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.
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!
See http://www.kegel.com/c10k.html for an in-depth, if old, discussion.
Not sure if Apache had some spin-wait loops or something, but if so then that was a bug in Apache, not a fundamental characteristic of doing synchronous I/O in threads.
As for Apache, it spins a number of processes up to a point. When there aren't any free ones, the clients don't get any data.
> For one thing, threads take up memory and address space, and create work for the scheduler.
It is the right answer. "Cores are waiting" is not the right answer.
https://stackoverflow.com/questions/22090229/how-did-whatsap...
Time for a mini blog post contained in a HN comment!
(NOTE: A lot these descriptions are simplified)
Back in the old days we had Apache and its ilk, who approached handling multiple clients by spawning 1 thread per client. The model was simple and effective ... until you had thousands of clients, which resulted in overloading the OS with too many threads.
So along came nginx and its ilk. Instead of a thread per client, they used epoll and a state machine per client. This allowed Nginx to handle a massive number of concurrent connections since the state machine was much smaller than a full thread's stack, and nginx could implement its own scheduler instead of the OS's thread scheduler. But it's a more complex system, because you have to manually engineer those state machines. For a web server serving static content or routing connections that's not a big deal. For the backend to a modern web application? Not so much.
Eventually the web was no longer static content with PHP/Java backends; it was responsive, dynamic, explosive. And with those new requirements we needed ways to build complex web servers that could handle thousands or more clients at once. Apache's model wouldn't work; too much wasted memory and the OS still struggled with large numbers of threads. nginx's model also wouldn't work; it required too much engineering.
A lot of ideas began floating around. Around this time NodeJS showed up and exploded in popularity. Partially because it made building these backends easier. No threads to worry about; no custom state machines. Just nests of callbacks! It was crude ... but it kind of worked. Callbacks hell was ... hell, but less challenging than custom epoll based state machines. And most importantly, it was lightweight compared to the threading model.
So we've been evolving from that middle ground. Javascript added Promises, which simplified callback hell. And then eventually Javascript added async/await.
Ultimately, though, those two evolutions are just different ways of expressing the same underlying thing: custom state machines. Ah! See, whether you write a callback hell, a Promise tree, or an async function in Javascript, it all compiles to a kind of state machine. A blob of state that we can store and transport around in our underlying concurrency framework, and ratchet forward when asynchronous events are delivered by the OS.
So really, async/await is just the epoll model pioneered by nginx, but instead of having to write the state machines by hand, we can express them as regular looking code. And in fact, behind the scenes, all implementations of async/await whether it be in Javascript or Rust, are driven by epoll (or similar equivalent).
And empirically we know that epoll based models are just more efficient in terms of CPU and memory. Trying to use the OS's threading model hasn't worked out; you need a whole stack for every concurrent operation you're trying to perform, and the OS's scheduler isn't designed for the kinds of workloads we'd offload on it.
I guess the short of the long of it is that threads are great, but our OS's handling of threads just isn't good enough. Engineers have decided that putting in the effort of writing state machines, whether from scratch or with modern conveniences like async/await, is worth the cost.
> AWS has provided hosting for release artifacts (compilers, libraries, tools, and source code), serving those artifacts to users through CloudFront, preventing regressions with Crater on EC2, and managing other Rust-related infrastructure hosted on AWS.
> Microsoft Azure has sponsored builders for Rust’s CI infrastructure, notably the extremely resource intensive rust-lang/rust repository.
At least on AWS' side, it was some individuals—me included—who sent emails to the right people internally & pushed on tickets to be prioritized. There was surprisingly little pushback.
I don’t personally use cargo vendor but seeing it up streamed is also a big plus, IMHO.
Unfortunately, the build times still seem to be close to four hours, which limits the amount of changes that can be merged.
I'd rather my commit sit in a test queue for several hours than push and cross my fingers like LLVM does it.
Small things like Option::xor() or default cargo run are signs more and more people are using it "for real".
Just curious; what is your practical use case for that method?
As an enthusiastic Rust user who is perhaps not as academic/intellectual as most of the Rust community, I often find Rust-related reading material quite daunting. But never these. They're simple and they're great.
It wasn't hard to try out, the docs are here (including using it via cargo): https://doc.rust-lang.org/rustc/profile-guided-optimization....
https://github.com/rust-lang/rust/issues/61978
which puts it at over double the size of Go. Worse is seems to be no impetus to fix this. Also notable is Rust still doesnt have a Map literal:
https://sourceforge.net/projects/tdm-gcc/files/TDM-GCC%20Ins...
vs 299 MB:
https://static.rust-lang.org/dist/rust-nightly-x86_64-pc-win...
I am a coder but I'm not well versed in Rust enough to be able to contribute code, but i'm gonna try to investigate and find other venues I might be helpful (writing documentation, translation to the languages i speak, etc).
What are some good open source projects built in rust which wouldn’t be too difficult to contribute to?
I also like that the compiler issues are tagged by difficulty and whether a mentor is available, fantastic.
Thank you!
Looks like a lot of fun :)
Why didn't Rust go with an easier to read syntax? Why did they stick to old C syntax?
Edit/ Sorry, didn't mean to offend anyone.
We did diverge where we felt it was appropriate.
The only C style syntax that's used is block grouping (curly braces), and statement separation (semicolon). Unless you go for using whitespace to perform those functions, you're making fairly arbitrary choices about which symbol to use, and those two are familiar.
Most other syntax is really quite different:
* variable definition has a completely different order to it with (optional) type ascription
* function definition has some aspects of C++ (<> for types in generic functions) but much else is different - e.g. position of return type, where clauses, the 'fn' keyword, type ascription
If anything, Rust's syntax can be confusing because the order of names and types are reversed compared to C. This is a trend that a lot of recent languages are returning to because it can simplify the parser.
Also backtick lifetime parameters, but that's a feature that no other mainstream language has, so it was bound to be a little confusing regardless of the syntax.
Of course it has several critical extensions related to impl, generics, lifetimes, references, modules etc, partly influenced by C++ (e.g. generics syntax), Ruby (closure) and Python (self). It depends on your perspective, but if the scope is all programming languages, then Rust is syntactically very close to C.
Off the top of my head, I'd imagine that familiarity for the huge pool of C/C++/Java/C# programmers was probably a huge push for the chosen syntax.
* Ending statements with ;
* {} for braces, lowercase keywords for control flow
* [] and 0-based indexing for arrays
* Infix operator notation, with mathematical operator precedence
* &, |, ^, ~ for bitwise operators
* . is used for member access
But they didn't copy all of C's syntax:
* There's no ->, since dereference is usually automatic.
* No ?:, if/else can be used as expressions instead
* No ~, since ! on an integer variable does bitwise negation instead.
* Pointer, array, and function types don't have the weird syntax they do in C... you say [ * mut fn()->i32; 4] instead of int ( * ())[4]
* Types follow variables instead of precede them (x: i32 instead of int x)
* No C-style casts (x as i32 instead of (int)x)
Well, there is, but it's more like ML’s -> than C’s ->.
So what it should actually be is int ( * [4])(). The principle is that "declaration mimics use" and that a type is written like a declaration with the variable name omitted. So, for an array of pointers to functions mapping no-args to int:
If f is such a function, you would call it as f() and get an int, so the declaration would look like int f() and the type would be int () except that functions, as opposed to function pointers, aren't first-class objects in C and don't have a type.
If pf is a pointer to such a function, you would need ( * pf) instead of f, so the declaration would be int ( * pf)() and the type would be int ( * )().
If apf is an array of those, then you would need to replace pf with apf[something], so the declaration would be int ( * apf[4])() and the type would be int ( * [4])().
Which is, indeed, a bit weird.
(Note: Spaces around all the asterisks because that seems to be the least-bad way of making HN not interpret them as markup. It's quite impressive how damaging HN's markup feature manages to be given how little it's capable of.)
C's function pointer and array syntax does make sense when you understand it, but even for experts, it can be difficult to get it right on the first go.
However "hard to read" is also the many concepts that rust have and certainly the combination of traits/lifetimes make some stuff very obtuse.
Did they? Rust always seems to me to a pretty free mix of C, Ruby-ish, and ML syntax (and an amazingly well chosen mix, because that description makes me want to run away from it, but the reality is pretty nice.)
By all means I feel rust still compile too slowly, if it has faster build time I might spend some time playing with it.
- https://github.com/japaric/rust-cross (more involved)
- https://github.com/rust-embedded/cross (straightforward)
(Edited for formatting)