More fundamentally Rust is poorly suited to programs that don't have a hierarchical ownership model. UI programming is a good example. UI elements tend to have lots of cross-references to each other: think event handlers, etc.
Of course Rust can support non-hierarchical ownership, but only by applying constraints that don't make sense in UI programming. Imagine if JavaScript's `getElementById()` threw exceptions if the referenced element was already on the stack. That would be silly! In this context, Rust's borrow checking is not a seatbelt but a footgun.
In theory this is problematic, but I'm not sure how often it comes up in practice: a lot of systems-y use cases for dynamic linking are writing low-level libraries that many other languages can use, which currently means exposing a C interface. This is supported and stable in Rust.
Of course, this doesn't cover every thing, e.g. pure Rust plugins for pure Rust programs. Although one still can dynamically link, as long as all pieces are compiled with the same version (annoying, but, depending on circumstances, not a complete roadblock due to tooling like rustup).
> Imagine if JavaScript's `getElementById()` threw exceptions if the referenced element was already on the stack
I'm sure you realise it, but Rust does offer a wide variety of functionality for working with shared data. It is definitely syntactically more verbose than in languages that don't try to tame mutation, but it's essentially equivalent functionality (at least, to languages that use reference counting).
UIKit is an example of a high-level dynamically linked library. Rust isn't ready for that category of use case - but to be fair neither is C++ :)
> Rust does offer a wide variety of functionality for working with shared data. It is definitely syntactically more verbose than in languages that don't try to tame mutation, but it's essentially equivalent functionality
My point regarding shared data is not about Rust's verbose syntax, but about its unwanted runtime checks which no other languages perform.
Here's what I have in mind (doing my best pseudo-Rust):
struct CounterWidget {
count: u32,
limit: u32,
didClick: fn(),
};
impl CounterWidget {
fn clicked(&mut self) {
if self.count < self.limit {
self.count+=1;
didClick();
}
}
fn setLimit(&mut self, limit:u32) {
self.limit = limit;
}
}
The Counter widget has a count and a limit, and a callback for when it's clicked. It also has a landmine: if the click callback attempts to call setLimit on the same Counter, it will crash at runtime.This crash is hard to test for and doesn't add any value: why shouldn't the callback be able to adjust its limit?
You must mean that you stored your `CounterWidget` in a RefCell, which does have the semantics that if you mutate it while holding a mutable reference to it, it will crash. But your code sample didn't include a RefCell at all, so this is very unclear.
Yes, its true - Rust provides a hard guarantee against simultaneous mutable access to the same data. This is invaluable even in a non-concurrent context, both because of memory issues like iterator invalidation & higher level logic errors that result from silent mutation at a distance.
I can assume looking at your code that didClick() doesn't mutate this CounterWidget - or even access it. This means I know that if I rearrange the counter increment with the didClick callback, I know nothing is semantically different. I can introduce additional code to the function, knowing that the state is the same after didClick as before it. All of this information is very useful to me when I'm trying to maintain this codebase (not to mention useful to the optimizer).
If you want to be able to provide mutable access to the callback, just make it an `fn(&mut CounterWidget)`.
That's fair.
> My point regarding shared data is not about Rust's verbose syntax, but about its unwanted runtime checks which no other languages perform.
FWIW, I think Swift is the only language with implicit checks of this form (for global variables and class properties). Rust only performs such checks if the programmer opts into it by using types like RefCell or Mutex.
> It also has a landmine: if the click callback attempts to call setLimit on the same Counter, it will crash at runtime.
Specifically, the callback will be statically unable to call setLimit unless the programmer decides to use one of those tools, in which case the location of the crash is clear.
> This crash is hard to test for and doesn't add any value: why shouldn't the callback be able to adjust its limit?
That specific case seems like it's okay, but there's a pile of problems in code that is only slightly different. For instance,
- if `clicked` held a reference to an element of a vector stored inside CounterWidget across the callback call, allowing mutation would allow that reference to be invalidated (this is less contrived than it sounds: e.g. iterating through a vector calling the callback for each element has this problem). This is undefined behaviour.
- if the callback enables results in the data being manipulated on multiple threads, you get a data race. (Also undefined behaviour.)
- stepping away from undefined behaviour, there may be things inside the `if` statement after the callback that are relying on the invariant the condition established (e.g. `let remaining = self.limit - self.count` and assuming it will be > 0), the callback can invalidate this in surprising ways, both for the programmer, who didn't expect the circular chain, and for the compiler's, which is thus forced to be defensive/pessimistic, meaning slower code (this is one major reason why Swift adopted exclusivity).
Rust is a big sidestep in usability, which makes sense if you want to replace C and C++, but not much sense elsewhere. Humans are really, really good at juggling multile languages, it's why we make a new one whenever we can.
So, the reason to use one of these languages is to get away from the "you can use a GC"-space and then the count of interesting languages gets very small very fast.
But sure, if your problem is in the "GC can be used" space, but you like to use new languages .. go for it. It's certainly fun to use a new language to tackle a problem. Just not the most efficient use of someones time in my experience. (But: Fun!)
Unless you have a language with immutable data structures, ownership is still a problem with a GC.
E.g. Consider the case where you have some class with some member variable. You want to provide a getter to obtain the member. Now you have an ownership problem: you could return a reference, but now the caller could mutate the data structure in such a way that it breaks invariants of the wrapping class. So, instead you return a copy, but this can be prohibitively expensive.
There are applications where a garbage collector is simply unnecessary and possibly extremely detrimental.
And if you get down to it you'll find an amazing truth: once you learn how writing programs is about as easy without a garbage collector than it is with one and your programs run a lot faster.
Using an automatic transmission is a lot easier but manual is not hard and you save a lot of gas.
Back in the 90s, people were thinking Java would be The One. Didn't happen. Good luck with convincing everyone that it should be Rust and not their preferred language.
If you only need an spa use react or bue. If you only need a website use php or ruby. If you only need a webservice use node or go. If you only need an operating system use rust or c.
Using rust for client side validation is probably not going to work.
Omg, what are gRPC, avro, thrift, and cap'n proto meant to do?
I bet if we could all just agree to pick one of those then the interoperability problem will be solved. I know you were referring to C ABI etc, but as others have pointed out, having two GC languages communicating with each other over a C ABI is tough, which is why IPC/RPC exist.
Oh, and once we settle this single ABI/Interoperability problem, let's come back to this one-true-language idea... I think there's something there ;)