Hacker Newsnew | past | comments | ask | show | jobs | submitlogin

Callbacks are often isopmorphic to a big ball of mutable state, which Rust makes very painful.

It’s like the classic Joe Armstrong quite about getting the whole jungle when all you wanted is the banana.

You can do it, and if you check out the Gtk/QT bindings you’ll see the boilerplate it introduces. Not insurmountable but many people are interested in figuring out if there’s a better way.



I’m sure I’m being naive, but could this be worked around by passing a mutable reference to the state into the callback rather than it closing over the state? Assuming there’s only one UI thread, then only one callback can run at once anyway…


I think the fundamental problem with that approach is that even in single threaded code rust makes it illegal to have to have two pointers where at least one is mutable to the same state. So I can pass in a `&mut State` pointer, but then I can't also pass in a `&mut AnElementInSomeListInState` pointer. Nor can I really have `AnElementInSomeListInState` just have a parent pointer to the rest of the state, because someone has to have a `&mut State` pointer, and they would conflict (also because doubly linked lists are hard in rust).


The problem is that multiple callbacks might require a reference to the same state, and closing over those references and retaining that callbacks, makes two mutable references to the same state, which Rust makes an illegal pattern. That is indeed safe in single-threaded code, but Rust prevents it nevertheless; there is a famous (in Rust circles) blog post about this: https://manishearth.github.io/blog/2015/05/17/the-problem-wi...

There is a workaround called "internal mutability", an ability to mutate state pointed by a shared pointer. That is syntactically slightly messier, and generally frowned upon. The blog post mentions about this workaround, and also states that it's not ideal so we should strive for better.


> There is a workaround called "internal mutability", an ability to mutate state pointed by a shared pointer. That is syntactically slightly messier, and generally frowned upon.

I don’t see why this would be frowned upon. It seems to be a runtime version of the borrow checker. For sufficiently dynamic code I don’t see how you can get around checking mutability access at runtime (or asserting that multiple blocks of code aren’t concurrently requesting a mutable reference).

As a comparison, there are many cases where the compiler can prove that a bounds check is unnecessary for accessing an element in a vector but there are also many useful cases where it’s simply not possible to do at compile time. It would be silly to frown upon runtime bounds checks when the requested index or the size of the vector is not known at compile time, a common occurrence in many interesting programs.

I get the motivation to make APIs as statically checkable as possible but it doesn’t seem to always be practical. Reusable UI components can be used in a variety of contexts, e.g. situations with multiple callbacks for different backends. The information is just not always there at compile time.


As someone who had literal paid gigs in c++ where I had to remove all sorts of reference counts to reach performance targets, it's depressing that there doesn't seem to be a better way in rust


The way to do this in Rust is to use “unsafe.” It essentially means “trust the programmer that this pointer is live.”

I don’t think there is a way around this. You’re dealing with a high-level runtime condition. There are no tractable ways to get compilers to understand these higher level runtime conditions, so conditions must be reproven at runtime. It needs an oracle. That oracle should be you but you’ve decided that you cannot be trusted. This appears to be somewhat of a contradiction. Should you even be writing this program in the first place?


The reason it's frowned upon is that it makes the code not thread safe. This is fine in application code, but if you are a library author, you generally want your code to be as flexible as possible, and able to be used in various situations. Making the code not thread safe brings upon some limitations how the code can be used by your users. This is why using internal mutation sparingly is the default.


I’m not clear on what you mean it’s not thread safe. Arc<> is thread safe.




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

Search: