https://mail.mozilla.org/pipermail/rust-dev/2013-April/00355...
https://github.com/rust-lang/rust/issues/217
> I'm sorry to be saying all this, and it is with a heavy heart, but we tried and did not find a way to make the tradeoffs associated with them sum up to an argument for inclusion in rust.
> -Graydon
https://github.com/rust-lang/rfcs/issues/271 is a better link today.
return as foobar(x, n-1)
return in foobar(x, n-1)
override return foobar(x, n-1)
final return foobar(x, n-1)
It's a little surprising to see entirely new construct invented for something that is just a different implementation of `return`, after all, the end result of computation with tail-return is the same as with normal return, only performance differs.I might be wrong here but it always seemed to me that Rust was built to replace C/C++ in critical infrastructure like Firefox, Nginx, Redis, etc. Basically critical network dependent infrastructure.
With respect (because I understand the difficulties that come with time constraints/resourcing/building an efficient async API), currently I'm not sure how Rust expects itself to be a viable replacement (let alone the best replacement) language for any of those types of applications.
It's important to remember that IO is truly a library concern in Rust. 1.0 means the language is stable, but there's still tons of libraries to build on top of that language. Holding back the language itself for a library that, while important, is only needed for certain applications wouldn't make a whole lot of sense.
The argument might be similar to using benchmarks for a parallel web rendering engine (a Servo-like) in Go. It would probably have similar median latencies to Servo. And Go may even scale out better if tabs were sandboxed in goroutines instead of processes. However, ofcourse unlike Servo it would have a higher variance/p90/p99 latencies because of the GC. P.S. I'm not a big fan of Go I was just using it to try and bolster my argument that Servo's current benchmarks shouldn't be used as an argument against async io prioritization. Actually, side note, I would love to hear more flaws about building a Servo-like in Go.
https://www.reddit.com/r/rust/comments/1v2ptr/is_nonblocking...
The timing of the move away from green threads didn't really offer enough time to implement a stable async IO option before 1.0
Seriously, nearly all data storage formats we use today have some kind of version number in them - why are we treating code as dumb text rather than interesting data?
In C++, this would look like:
#include <iostream>
class B { public: int get_id() { return id; }; int id; };
class C : public B { };
int main() { C c; std::cout << c.get_id() << "\n"; return 0;}
Maybe I'm ignorant, and there's an easy way to do this. I was screwing around with Rust a few months before 1.0 and didn't see any mention of it in the docs, though. (Everything I saw required me to provide an impl for functions defined in an interface for every class that implemented that interface.)Rust provides this. E.g., the "talk" method in the "Animal" trait below: [0]
trait Animal {
// Static method signature; `Self` refers to the implementor type
fn new(name: &'static str) -> Self;
// Instance methods, only signatures
fn name(&self) -> &'static str;
fn noise(&self) -> &'static str;
// A trait can provide default method definitions
fn talk(&self) {
// These definitions can access other methods declared in the same
// trait
println!("{} says {}", self.name(), self.noise());
}
}
[0] From Rust By Example, http://rustbyexample.com/trait.htmlA question (In C++ syntax, as I'm not a Rust programmer): How would one call Animal::talk inside Dog::talk? The obvious thing (commenting out the println and adding Animal::talk(self) ) causes infinite recursion. The other vaguely obvious thing ( Animal.talk(self); ) is a syntax error, which makes sense.
Edit: I'm referring to the code at the linked Rust By Example page.
There is no way to call the overwritten `Animal::talk`.
Animal::talk(self);The default implementation, if overridden, does not exist for the given type.
pub fn super_talk<T:Animal>(this: &T) {
println!("{} says {}", this.name(), this.noise());
}
trait Animal {
//...
fn talk(&self) {
super_talk(self)
}
}I'd also question why the module system is "braindead", of course.
I don't recall why incremental compilation requires a breaking change, maybe it doesn't. I shouldn't have looped it into that statement without being certain.
The module system _is_ braindead, though. Last time I brought this up, I made a proof of concept that compiled the exact same program two ways - one by using the module system, and one by running the code through the C preprocessor and literally #include-ing other rust files. The purpose of this PoC was to point out that Rust modules are functionally identical to #include-ing C files would be in C, which is a well known antipattern.
Rust is very close to C when you consider the guts of the toolchain. C has solved several problems with respect to linking and I feel like Rust could have taken several more hints from C. This is probably a consequence of the Rust devs inherently disliking C and wanting to distance themselves from it.
But they trivially aren't.
lib.rs:
mod foo;
mod bar;
foo.rs:
fn f() {}
bar.rs:
fn f() {}
No name conflict. foo::f and bar::f happily coexist.In C:
lib.c:
#include "foo.c"
#include "bar.c"
foo.c:
void f() {}
bar.c:
void f() {}
Name conflict; fails to compile.> Rust is very close to C when you consider the guts of the toolchain. C has solved several problems with respect to linking and I feel like Rust could have taken several more hints from C. This is probably a consequence of the Rust devs inherently disliking C and wanting to distance themselves from it.
No, it's that header files are are a big problem in C (DRY violation, hostile to code inlining, slow compilation) and it was felt that a real module system would be an improvement.
mod foo {
#include "..."
}
Which is barely enough to say that Rust is hugely different than just #include-ing C files.I was talking more about linking objects incrementally and the consequences of that design, rather than singing the praises of headers (though I do rather like headers). I understand C from the compiler's perspective as well, having written my own linker and assembler from scratch myself, and I really appreciate the elegance of the design.
>slow compilation
That's objectively untrue. It's much faster to compile with something like headers.
As far as inlining and DRY are concerned, back before Rust shipped I spoke with many Rust maintainers about solutions to all of these problems, but it was dismissed because "we're trying to ship". Maybe you shouldn't sail a boat when you need to replace the hull later?
Well, sure, if you want to get fancy enough you can make a module system out of #include. (Your code snippet isn't enough because it doesn't replicate privacy or imports.) But replicating something approximating Rust's module system with "#include plus other stuff" doesn't show that Rust's module system is "just #include".
> That's objectively untrue. It's much faster to compile with something like headers.
I don't think that's true once you have the proper incremental build setup. With headers, you have to parse large source files over and over again. With a proper module system, the compiler can use a more efficient binary database format (with an index).
For example, consider math.h. If your program is using one function (say, sin) from math.h, you have to parse all the prototypes in math.h. (And if you have inlined functions or templates in the header files, you have to parse those too!) But with a module system, the compiler can serialize an index of all the signatures of functions inside libmath.so, so the compiler can do a direct, O(1) hash table lookup for "sin".
We aren't there today, of course, since we don't have incremental compilation, and C is certainly simpler, but I think doing it right from the start will pay dividends down the road.
> As far as inlining and DRY are concerned, back before Rust shipped I spoke with many Rust maintainers about solutions to all of these problems, but it was dismissed because "we're trying to ship". Maybe you shouldn't sail a boat when you need to replace the hull later?
I don't see anything backwards-incompatible about incremental compilation, and I believe where we're going will end up better than header files when all is said and done.
Isn't this pretty much a solved problem with precompiled / pre tokenized headers? PTH are language / arch / compiler agnostic.
> Modules making it into C++17 is less likely.
However, they do say > That said, from a user’s perspective, I don’t think this is any reason to
> despair: the feature is still likely to become available to users in the
> 2017 timeframe, even if it’s in the form of a TS.I don't see that as a bad thing though, it's not great if you are trying to write Rust programs/libraries -right- now as sometimes you will have to jump through some hoops to only use stable Rust APIs but it will be worth it in the long term.
Personally I'm starting to dislike index operations since they can panic (and are the shortest way to access), and I rather use the explicit Option based APIs. Though I'm not too worried about those, since a lint disallowing them shouldn't be too hard.
Are you sure about that? One of the main advantages of coroutines/greenlets IMO is writing simple and straightforward blocking code (e.g. an echo server); without them, you either need to use threads (which are slower and much more heavyweight) orcallbacks or related constructs (async/await, futures, ...).
Even if it could, many real-world servers actually do non-trivial work in their threads, so the cost of spawning a thread is dwarfed by the actual work the thread ends up doing. There are serious drawbacks to M:N: complexity, fairness, problems in interoperability with the 1:1 world (including essentially unavoidable performance problems with the FFI), etc.
There is a Rust library called "mio" which provides a lot of the plumbing for such systems: https://github.com/carllerche/mio.
The path forward is likely going to involve adding a way to build cheap state machines (call them generators or async/await) with a clean syntax and giving mio hundreds of thousands of reusable instances.
I don't understand, and the link seems unclear. Perhaps a more direct question: I get a request X, and I need to consult a backend service to answer the request. Do I write synchronous code calling that backend? Or do I have some callback mechanism?
> ... generators or async/await
Ah. This perhaps answers my question. Both of these are essentially compiler-written callbacks.
If this is going to be like C#, then I presume there will be a thread-pool where user code will execute. It seems like a non-ideal story for concurrency. Users will have to take inordinate care not to call any blocking code; otherwise they will prevent one of the threads in the pool from doing useful work.
The downsides of going M:N are worse. The cgo-like FFI performance problems, for example, are killer for Rust's use case.
To be honest it seems to me like your explanation is an attempt to downplay just how nice fibers/coroutines are rather than acknowledge their utility in many existing languages.
Userspace is not equipped to make reasonable scheduling decisions that provide any significant performance advantage, and library/language runtime control of M thread register/stack contexts on top of N kernel threads plays absolute havoc with most operating system's standard libraries.
Go works around this by explicitly not calling into libc et al -- all system calls are issued directly. One big problem with that: directly invoking syscalls is supported on Linux, but NOT supported on OS X.
End result is that Go literally must rely on undefined behavior on any system that does not support direct issuing of syscalls.
From my brief review just now, what MS appears to be proposing for C++17 isn't coroutines in the traditional M:N threading sense, but rather, an explicit mechanism (with syntactical sugar) for capturing reachable variables in a lambda (without preserving the stack), and issuing a call to that magicked-up lambda later via promises.
This is interesting if you love the idea imperative mutable promise-based concurrency, but it's not likely to win you any performance gains, and it's useless in the extreme if imperative mutable promises aren't your cup of tea.
But since Rust is intended for runtime-less lower-level programming it doesn't make any sense here.
let mut arr = [1, 2];
let a = &mut arr[0];
let b = &mut arr[0];
This code could be allowed: let mut arr = [1, 2];
let a = &mut arr[0];
let b = &mut arr[1];
And it gets Hard once variable indexes are involved. And since the whole point of using arrays is to get variable indexing, the Rust developers choose to only implement reborrowing for structs and tuples. fn main() {
let mut x = 10;
let y = &mut x;
*y = 11;
println!("{}", x);
}
This will complain error: cannot borrow `x` as immutable because it is also borrowed as mutable
This is because an `&mut` borrow is exclusive: while `y` is alive, we cannot use `x`. We can fix this by making a new scope for `y`: fn main() {
let mut x = 10;
{
let y = &mut x;
*y = 11;
}
println!("{}", x);
}
This works, and will print `11`.Non-lexical lifetimes would allow the compiler to demonstrate that these two things are the same, and allow the first one to compile with the behavior of the second.
It's an interesting tradeoff, because right now, the rules are very simple and conservative. Scope is fairly easy to reason about. Non-lexical lifetimes would make certain things easier, but also a bit harder to reason about, because the rules are more complex.
I want memory safety with as few hazzle as possible and that Rust doesn't understand the safety of the first example means hazzle. I expect the compiler to try to understand even if it's ’hard’. It doesn't need to understand everything but the mentioned situtation should be doable.
I'm not sure what you mean by a 'more detailed tradeoff.' You mean a more complicated example?
> I expect the compiler to try to understand even if it's ’hard’.
It's not a matter of difficulty, exactly, it's a matter of how easy it is to understand what the compiler is doing. Figuring out non-lexical scopes means that my mental model of what the compiler is doing is more difficult than it is right now, which may or may not be the right tradeoff. I would say that most people want non-lexical lifetimes/SEME regions to be implemented, though.
A safe stdlib - aborting on malloc failure is not safe.
Abort occurs on oom, that's right and also on double panic (most frequently found if a destructor panics during unwinding).