error[E0271]: type mismatch resolving `<futures::AndThen<futures::Select<futures::stream::ForEach<futures::stream::MapErr<std::boxed::Box<futures::Stream<Error=std::io::Error, Item=(tokio_uds::UnixStream, std::os::ext::net::SocketAddr)> + std::marker::Send>, [closure@src/server/mod.rs:59:18: 59:74]>, [closure@src/server/mod.rs:60:19: 69:10 next_connection_id:_], std::result::Result<(), ()>>, futures::MapErr<futures::Receiver<()>, [closure@src/server/mod.rs:74:18: 74:74]>>, std::result::Result<(), ((), futures::SelectNext<futures::stream::ForEach<futures::stream::MapErr<std::boxed::Box<futures::Stream<Error=std::io::Error, Item=(tokio_uds::UnixStream, std::os::ext::net::SocketAddr)> + std::marker::Send>, [closure@src/server/mod.rs:59:18: 59:74]>, [closure@src/server/mod.rs:60:19: 69:10 next_connection_id:_], std::result::Result<(), ()>>, futures::MapErr<futures::Receiver<()>, [closure@src/server/mod.rs:74:18: 74:74]>>)>, [closure@src/server/mod.rs:78:34: 83:6 cfg:_]> as futures::Future>::Error == ()`
--> src/server/mod.rs:85:15
|
85 | return Ok(Box::new(server));
| ^^^^^^^^^^^^^^^^ expected tuple, found ()
|
= note: expected type `((), futures::SelectNext<futures::stream::ForEach<futures::stream::MapErr<std::boxed::Box<futures::Stream<Error=std::io::Error, Item=(tokio_uds::UnixStream, std::os::ext::net::SocketAddr)> + std::marker::Send>, [closure@src/server/mod.rs:59:18: 59:74]>, [closure@src/server/mod.rs:60:19: 69:10 next_connection_id:_], std::result::Result<(), ()>>, futures::MapErr<futures::Receiver<()>, [closure@src/server/mod.rs:74:18: 74:74]>>)`
found type `()`
= note: required for the cast to the object type `futures::Future<Item=(), Error=()>`
I have hope that things will improve on this front when `impl Trait` lands.EDIT: After re-reading this, I want to add that I don't mean to hate on Tokio. I like the basic design very much, and hope that they can work out the ergonomics issues and stabilize the API soon.
It would also encourage organizations large and small to start releasing complete, polished products instead of the "move fast and break things" crap that has infected the industry.
Imagine if car makers worked the same way.
But that's just me - many people need these 0 cost abstractions, this is Rust's focus, and I'll have to wait for the higher level stuff for my projects.
My personal interest in Rust is as a C replacement, including as a replacement for existing libraries that are written in C. While I agree threads can be very efficient (after all, the kernel implements threading by being async itself, more or less), they're annoying for this use case.
Doesn't almost every major programming language have Futures though? (C++, Java, Python, Ruby, JavaScript, Go).
It seems a fair criticism for so common a building block.
I would agree with the author's criticism if it was "futures are in the lang nursery and still not ready to use," rather than: Rust is bad, because I got nasty errors when using this work-in-progress library.
The problems right now start when you want to go async. I follow the development because I want to see easy, safe and fast way of writing async programs, and there's lots of interesting development happening with Rust.
|
212 | / fn call(&self, payload: Self::Request) -> Self::Future {
213 | | let request = self.create_request(payload);
214 | |
215 | | let work = async_block! {
... |
254 | | FutureResponse(Box::new(work))
255 | | }
| |_____^
note: ...so that the type `impl futures::__rt::MyFuture<<[generator@src/client.rs:215:20: 252:10 self:&client::Client,
request:hyper::Request for<'r> {futures::Async<futures::__rt::Mu>, (), fn(std::result::Result<hyper::Response, error::Error>) -> std::result::Result<<std::result::Result<hyper::Response, error::Error> as std::ops::Try>::Ok, <std::result::Result<hyper::Response, error::Error> as std::ops::Try>::Error> {<std::result::Result<hyper::Response, error::Error> as std::ops::Try>::into_result}, futures::MapErr<hyper::client::FutureResponse, [closure@src/client.rs:217:59: 217:85]>,
hyper::Response, std::option::Option<std::string::String>, &'r hyper::Response, hyper::StatusCode, fn(std::result::Result<hyper::Chunk, error::Error>) -> std::result::Result<<std::result::Result<hyper::Chunk, error::Error> as std::ops::Try>::Ok, <std::result::Result<hyper::Chunk, error::Error> as std::ops::Try>::Error> {<std::result::Result<hyper::Chunk,
error::Error> as std::ops::Try>::into_result}, futures::stream::Concat2<futures::stream::MapErr<hyper::Body, [closure@src/client.rs:234:49: 234:75]>>}] as std::ops::Generator>::Return>` will meet its required lifetime boundsAnyways, I think it has still long way to go to match the length of even common C++ template related error messages...
And latest C++14 and C++17 changes also help library writers to error check the type parameters.
Of course those that have to use other compilers still need to face the sea of incomprehensible error messages.
And in any case, better stay away from template meta-programming libraries, at least until modules and concepts eventually land.
Or you know, it's aiming nothing of the short, and this is early, still unsorted, behavior, while the language has been simplifying things (e.g. the early sigils and lots of other stuff), and plans even more simplification and friendliness.
https://jvns.ca/blog/2018/01/13/rust-in-2018--way-easier-to-...
This is so awesome.
I felt like all type errors are backwards. That is, "got" was the target you are giving your type to, not the type that you are passing. This may only happen in some cases, but I just started tuning the content of those errors out and instead adjusted randomly until things worked or the message changed.
I was often getting obscure type errors that were not at all related to the issue, and sometimes the compiler just insisted that just one more burrow would do, no matter how many burrows you stack on. This is definitely because I did stupid things, but the compiler messages were only making matters worse.
String vs. str is a pain in the arse. My code was littered with .as_str() and .to_string(). I never had the right one.
Enums are super nice, but it's very annoying that you cannot just use the value as a type. My project had a lot of enums (user-provided query trees), and it was causing a lot of friction.
There are also many trivial tasks where you think "Of course there is an established ecosystem of libraries and frameworks for this", and end up proven wrong. I mostly did find one library for the thing I needed, but often immature. The HTTP server + DB game seems especially poor.
In the end, I had to quit the fun and get work done (and others did not find playing with new tools as fun as I did), so I ported the project to Go and got productive. I took a fraction of the time to write in Go, the libraries are just so much more mature, it performs significantly better than the Rust implementation (probably because of better libraries—definitely not stating that Go is faster than Rust here), compile takes 1 (one) second rather than minnutes, and there is in general just much less friction.
On the flipside, it takes about 2-3 times as much Go than Rust to do the same task, even if it was way easier to write the Go code. The code is also a lot uglier. As an especially bad case, something that was a serde macro call in the Rust version is 150 lines of manually walking maps in the Go version.
Note that OCaml has this feature of using-a-enum-value-as-a-type (or using a subset of enum values as a type, etc.). It works very well, but it quickly produces impossibly complicated error messages.
I'd like Rust to have this feature, eventually, but not before there is a good story for error messages.
PSA: If you have a variable that's a String, you can easily pass it to anything that expects a &str just by taking a reference to it:
fn i_take_a_str(x: &str) {}
let i_am_a_string = "foo".to_string();
i_take_a_str(&i_am_a_string);
Every variable of type &str is just a reference to a string whose memory lives somewhere else. In the case of string literals, that memory is in the static data section of your binary. In this case, that memory is just in the original allocation of your String.I don't remember how I found out, but it seemed oddly magical until I just now read the docs: String implements Deref<Target=str>. Makes more sense now.
I still had a bunch of to_strings()'s, though, as things tended to take String whenever I had &str's. I found this to be a very unexpected nuisance.
EDIT: Maybe I needed as_str() as the & trick doesn't work if the target type cannot be inferred as &str?
And yeah, Deref doesn't kick in everywhere, so you may need the .as_str() in those situations. It should be the extreme minority case, generally. Same with .to_string(), though moreso. Most stuff should take &str, not String.
Functions should prefer &str or perhaps T where T: AsRef<str>. Note that if you write code that needs an owned String, you could consider taking some T where T: Into<String>, because this allows you to take many kinds of string types, such as &str, String, Box<str>, and Cow<'a, str>.
Your suggestions make sense, but I can't help but think that there is something fundamentally weird about basically having to use generic programming just to take a string arg. The most sensible thing would be "everything uses &str".
Indeed, it's the hard trade-off to make every time need a string reference, do you want a function that is slightly more general or one that has an easy-to-read signature?
It becomes even more interesting when defining trait methods. E.g.:
trait Hello {
fn hello<S>(who: S) where S: AsRef<str>;
}
Has the benefit of being more general, but cannot be used as a trait object (dynamic dispatch), since it wouldn't be possible to generate a vtable for &Hello.I generally prefer the generic approach for functions/methods that want to own a String, since using some_str.to_owned() to pass something to a method is not very ergonomic (relative compared to &owned_string for a function that takes &str). But you have to be certain that you don't want to use the trait as a trait object.
I find it really awkward that the error is a concrete type, making it so that you must convert all errors to "rethrow". Go's error interface, and even exception inheritance seems to have lower friction than this.
use error::{Error, ErrorKind, Result, ResultExt};
fn some_func(v: &str) -> Result<u32> {
v.parse::<u32>().chain_err(|| ErrorKind::ParseIntError)
}
The purpose of `chain_err` here is to add on top of the previous error, to explain what you were trying to do, instead of passing up the previous error (in this case, `std::num::ParseIntError`).If you don't like that, you can do something like this:
use std::boxed::Box;
use std::error::Error;
fn some_func(v: &str) -> Result<u32, Box<Error>> {
v.parse::<u32>().map_err(|e| Box::new(e))
}
But then you'd have to box every error.... I think. I don't remember it that vividly.
Having to implement a bunch of From traits (unless you need io::Error, because everything seemed to have conversions from/to that), or having to implement inline error conversion through map_err, is such friction. I might go as far as consider it the most cumbersome error system I have used. Clean idea, cumbersome implementation.
My comment about '?' not working with mismatched types was mostly just to say that it doesn't fix anything, it just adds a bit of convenient syntactic sugar.
Prototyping and editing code makes for most of my work, and Rust makes that a chore. That’s my main gripe with the language. It’s more like moving through molasses than encountering a brick wall.
Influenced by Rust's success bringing affine types into mainstream.
To keep using automatic memory management as their default way of managing memory, while offering some escape hatches based on affine types for low level optimizations.
But I'll agree that Rust is unpleasant for prototyping. What I find myself doing a lot when starting out a project is just figuring out if some snippet of code will work. There's no REPL to just run it in. Then I have to either set up a scaffolding project just to run it, or just shove it somewhere along the working path and move it to it's real spot later. Except sometimes that messes up the borrow, or the signature and I have to decide between temporarily altering my working code to accommodate this small test or writing mode code without testing to reach the next test point.
And after everything works with your scaffolds and shims, you have to rip it all out and put your snippet where you wanted it in the first place. And then fill in all the gaps that prevented you from testing that snippet where it is in the first; hopefully it works, otherwise you're backtracking and rebuilding scaffolds you just ripped out.
There are times I just want to write a function and not declare return type, and not have the compiler complain about non-exhaustive matching cause there's only one usage and it's output is going straight into a `println!("{:?}", thingy)` anyway.
I guess the C++ equivalent is: I know when the code reaches this point it'll segfault and blow up, but I don't care because if it made it that far that means the thing I'm prototyping ran and gave me some feedback that I could act on. Rust just forces you to write everything instead of just things up til the prototype point.
That's disingenuous. It's really not as simple as you're trying to make it sound. But I don't understand the point of this guy's blog post, unless it's just to whine. There's so little real content in his post.
The author introduces himself as having a Python background, and as someone who experienced some challenges wrapping his mind around SQL. I wouldn't expect anything different.
And I also find that SQL can be quite infuriating to deal with, and had a high-friction experience with Rust.