Hacker News new | past | comments | ask | show | jobs | submit | gsserge's comments login

> It was not nearly as obvious 15 years ago, as it is today, but all the signs were there. We (I include myself here) were just not listening closely enough to the countries that knew Russia the best: Estonia, Latvia, Lithuania, Poland, Ukraine.

After living in Western Europe for almost a decade it's still shocking to me how much people here do not understand Russia.


In my experience, right here on HN the dominant narrative was that opposing Russian gas infrastructure was just American protectionism for frackers.

Similarly, opposing Chinese built telecom infrastructure is just seen as an attempt at American protectionism. Hopefully that continues to be a viable position, unlike depending on Russian gas.


At the very least Germany could have said no to Nord Stream 1 & 2.


Fiberplane | Rust Engineer, Frontend Engineer, Solution Engineer | Amsterdam, The Netherlands | ONSITE or REMOTE (CET +/- 3 hours) At Fiberplane (https://fiberplane.dev), we're redefining the future of collaboration for infrastructure teams, starting with collaborative notebooks for resolving incidents. Our vision is to put a programmable SRE environment at the fingertips of engineers everywhere.

We previously built Wercker, a container native CI/CD platform that was acquired by Oracle.

Our current open positions:

- Rust Engineer https://fiberplane.dev/careers/rust-engineer

- Frontend Engineer https://fiberplane.dev/careers/frontend-engineer

- Solution Engineer https://fiberplane.dev/careers/solution-engineer

- Developer Advocate https://fiberplane.dev/careers/developer-advocate

- Customer Experience Specialist https://fiberplane.dev/careers/customer-experience-specialis...

Want to join us? Email martina(at)fiberplane.com. You're more than welcome to get in touch if you're interested in what we're building but don't match the above open positions.


Fiberplane | Rust Engineer & Solution Engineer | Amsterdam, The Netherlands | ONSITE or REMOTE (CET +/- 3 hours)

At Fiberplane (https://fiberplane.dev), we're redefining the future of collaboration for infrastructure teams, starting with collaborative notebooks for resolving incidents. Our vision is to put a programmable SRE environment at the fingertips of engineers everywhere.

We previously built Wercker, a container native CI/CD platform that was acquired by Oracle.

Our current open positions:

- Rust Engineer https://fiberplane.dev/careers/rust-engineer

- Solution Engineer https://fiberplane.dev/careers/solution-engineer

Want to join us? Email martina(at)fiberplane.com. You're more than welcome to get in touch if you're interested in what we're building but don't match the above open positions.


visx is great! It hides a lot of D3 complexity and provides a very composable React API. The D3 API are still there if and when you need them.


This. I also used it, works great.


Here's the actual course's link with lecture materials and labs: http://nil.csail.mit.edu/6.824/2020/schedule.html


I like to joke that the best way to encounter the ugliest parts of Rust is to implement an HTTP router. Hours and days of boxing and pinning, Futures transformations, no async fn in traits, closures not being real first class citizens, T: Send + Sync + 'static, etc.

I call this The Dispatch Tax. Because any time you want more flexibility than the preferred static dispatch via generics can give you - oh, so you just want to store all these async fn(HttpRequest) -> Result<HttpResponse>, right? - you immediately start feeling the inconvenience and bulkiness of dynamic dispatch. It's like Rust is punishing you for using it. And with async/await taking this to a new level altogether, because you are immediately forced to understand how async funcs are transformed into ones that return Futures, how Futures are transformed into anon state machine structs; how closures are also transformed to anon structs. It's like there's no type system anymore, only structs.

That's one of the reasons, I think, why Go has won the Control Plane. Sure, projects like K8s, Docker, the whole HashiCorp suite are old news. But it's interesting and telling that even solid Rust shops like PingCAP are using Go for their control plane. It seems to me that there's some fundamental connection between flexibility of convenient dynamic dispatch and control plane tasks. And of course having the default runtime and building blocks like net and http in the standard library is a huge win.

That said, after almost three months of daily Rust it does get better. To the point when you can actually feel that some intuition and genuine understanding is there, and you can finally work on your problems instead of fighting with the language. I just wish that the initial learning curve wasn't so high.


Definitely dynamic dispatch + async brings out a lot of pain points.

But I only agree that the async part of that is unfortunate. Making dynamic dispatch have a little extra friction is a feature, not a bug, so to speak. Rust's raison d'être is "zero cost abstraction" and to be a systems language that should be viable in the same spaces as C++. Heap allocating needs to be explicit, just like in C and C++.

But, I agree that async is really unergonomic once you go beyond the most trivial examples (some of which the article doesn't even cover).

Some of it is the choices made around the async/await design (The Futures, themselves, and the "async model" is fine, IMO).

But the async syntax falls REALLY flat when you want an async trait method (because of a combination-and-overlap of no-HKTs, no-GATs, no `impl Trait` syntax for trait methods) or an async destructor (which isn't a huge deal- I think you can just use future::executor::block_on() and/or use something like the defer-drop crate for expensive drops).

Then it's compounded by the fact that Rust has these "implicit" traits that are usually implemented automatically, like Send, Sync, Unpin. It's great until you write a bunch of code that compiles just fine in the module, but you go to plug it in to some other code and realize that you actually needed it to be Send and it's not. Crap- gotta go back and massage it until it's Send or Unpin or whatever.

Some of these things will improve (GATs are coming), but I think that Rust kind of did itself a disservice with stabilizing the async/await stuff, because now they'll never be able to break it and the Pin/Unpin FUD makes me nervous. I also think that Rust should have embraced HKTs/Monads even though it's a big can of worms and invites Rust devs to turn into Scala/Haskell weenies (said with love because I'm one of them).


Oh yeah, I can totally relate to the Send-Sync-Unpin massaging, plus 'static bound for me. It's so weird that individually each of them kinda makes sense, but often you need to combine then and all of a sudden the understanding of combinations just does not.. combine. After a minute or two of trying to figure out what should actually go into that bound I give up, remove all of them and start adding them back one by one until the compiler is happy.


Yep. Same. I've been doing Rust for years at this point (not full time, and with long gaps- granted), and it's exactly like you said: individually these things are simple, but then you're trying to figure out where you accidentally let a reference cross an await boundary that killed your automatic Unpin that you didn't realize you needed. Suddenly it feels like you don't understand it like you thought you did.

The static lifetime bound is annoying, too! I guess it crops up if you take a future and compose it with another one? Both future implementations have to be static types to guarantee they live long enough once passed into the new future.


Is there any chance of fixing Pin/Unpin via a later Rust edition?


I don't know. But the Rust team is VERY against introducing any breaking changes. So we're stuck with the semantics we have today.


In a systems context, where performance and memory ostensibly matter, why wouldn’t you want to be made aware of those inefficiencies?

Sure, Go hides all that, but as a result it’s also possible to have memory leaks and spend extra time/memory on dynamic dispatch without being (fully) aware of it.


I think Rust is also able to hide certain things. Without async things are fine:

    type Handler = fn(Request<Body>) -> Result<Response<Body>, Error>; 
    let mut map: HashMap<&str, Handler> = HashMap::new(); 
    map.insert("/", |req| { Ok(Response::new("hello".into())) }); 
    map.insert("/about", |req| { Ok(Response::new("about".into())) });
Sure, using function pointer `fn` instead of one of the Fn traits is a bit of a cheating, but realistically you wouldn't want a handler to be a capturing closure anyway.

But of course you want to use async and hyper and tokio and your favorite async db connection pool. And the moment you add `async` to the Handler type definition - well, welcome to what author was describing in the original blog post. You'll end up with something like this

    type Handler = Box<dyn Fn(Request) -> BoxFuture + Send + Sync>; 
    type BoxFuture = Pin<Box<dyn Future<Output = Result> + Send>>;
plus type params with trait bounds infecting every method you want pass your handler to, think get, post, put, patch, etc.

    pub fn add<H, F>(&mut self, path: &str, handler: H)
    where
        H: Fn(Request) -> F + Send + Sync + 'static,
        F: Future<Output = Result> + Send + 'static,
And for what reason? I mean, look at the definitions

    fn(Request<Body>) -> Result<Response<Body>, Error>;
    async fn(Request<Body>) -> Result<Response<Body>, Error>;
It would be reasonable to suggest that if the first one is flexible enough to be stored in a container without any fuss, then the second one should as well. As a user of the language, especially in the beginning, I do not want to know of and be penalized by all the crazy transformations that the compiler is doing behind the scene.

And for the record, you can have memory leaks in Rust too. But that's besides the point.


>It would be reasonable to suggest that if the first one is flexible enough to be stored in a container without any fuss, then the second one should as well

I don't think this a reasonable in Rust (or in C/C++). I 90% of the pain of futures in Rust is most users don't want to care about memory allocation and want Rust to work like JS/Scala/C#.

When using a container containing a function, you only have to think allocating memory for the function pointer, which is almost always statically allocated. However for an async function, there's not only the function, but the future as well. As a user the language now poses a problem to you, where does the memory for the future live.

1. You could statically allocate the future (ex. type Handler = fn(Request<Body>) -> ResponseFuture, where ResponseFuture is a struct that implemented Future).

But this isn't very flexible and you'd have to hand roll your own Future type. It's not as ergonomic as async fn, but I've done it before in environments where I needed to avoid allocating memory.

2. You decide to box everything (what you posted).

If Rust were to hide everything from you, then the language could only offer you (2), but then the C++ users would complain that the futures framework isn't "zero-cost". However most people don't care about "zero-cost", and come from languages where the solution is the runtime just boxes everything for you.


Thanks for the suggestion. I didn't think of (1), although it's a pity that it's not as ergonomic as async fn.

I kinda feel like there's this false dichotomy here: either hide and be like Java/Go or be as explicit as possible about the costs like C/C++. Is there maybe a third option, when I as a developer aware of the allocation and dispatch costs, but the compiler will do all the boilerplate for me. Something like `async dyn fn(Request) -> Result<Response>`? :)


In this example rust doesn't just make me aware of the tradeoffs. It almost feels like the language is actively standing in the way of making the trade offs I want to make. At least as the language is today. I think a bunch of upcoming features like unsized rvalues and async fns in traits will help.


> In a systems context, where performance and memory ostensibly matter, why wouldn’t you want to be made aware of those inefficiencies?

Perhaps, but a bigger problem is that lots of folks are using Rust in a non-systems context (see HN frontpage on any random day).


> I recommend this blog post: https://nick.groenen.me/posts/rust-error-handling/

This blog post is good indeed. The discussion on /r/rust linked at the end of the post is also worth reading.

Another good and light introduction is http://www.sheshbabu.com/posts/rust-error-handling/

And a more in-depth look https://blog.burntsushi.net/rust-error-handling/


One thing that is useful to keep in mind when considering boxed errors or error-like objects, e.g. anyhow::Error, is that they famously do not implement std::error::Error trait [1].

This could be a problem if you want to use a third-party API where generic functions or structs use std::error::Error in their trait bounds. For example, fn use_error<E: std::error::Error>(e: E) { ... } will not compile with a boxed error as the parameter.

[1] https://users.rust-lang.org/t/shouldnt-box-t-where-t-error-i...


Is remote limited to the US only or Europe is also fine?


At this time we're limited to US-based remotes. Sorry about that, and I've updated the post to make that clear.


Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact

Search: