Static mutable String

It will. I have no illusions that the Rust implementation will ever reach the same level as the C# one. I was reading the Rust documentation and decided that learning by doing was better, so I started implementing this app in Rust. I know it so well. I've also implemented it in Java for Android.

It's annoying for me that super simple things, no-issues, like preserving some data between calls to a function, require heavy artillery in Rust. It's bizarre in my eyes that people are building theories to prove that this insanity is optimal. But it doesn't matter. I think I have all the basic pieces under control, like HTTP access, regex, threads, timers, channels, and mutex. I can do it as well as in C#.

But it all collapses with the GUI. My C# app has a rather sophisticated GUI. When working with C#, you don't program your GUI, you draw it with a graphical editor, and then you plug in your functions to handle the menus, buttons, or whatever. Unfortunately, the Windows support for Rust that I have seen so far is worthless. It dates back three decades in terms of development technique. I will not do it.

This is rather an elementary phenomenon on the internet, so it is taken care of. When setting up an HTTP request, you define how long you are prepared to wait. It has never failed in C#, and reqwest in Rust certainly works equally reliably.

This is the complete opposite of simple and no-issues.

First of all, a function is just a piece of code which can be executed, it doesn't have a state. The fact that some global piece of state is attached to it is a complication and a violation of what a function is supposed to be. And yes, that piece of state is global, because the function itself is global. So anything could call that function and mutate that piece of state. What guarantees that that function will even be called from a single thread? Nothing.

You could argue that functions with state exists and that even Rust has them. But they're closures, not functions. They look like functions, but also carry some state with them, and of course having a mutable static closure is no different than having some mutable static string.

It isn't bizzare when you see the base goal of Rust: having a system language, where the costs you pay are explicit, that is also memory and thread safe. This means that the compiler guarantees that if your code compiles it is memory and thread safe, not that it will do a best effort. And it also won't make stuff magically work by hiding the required stuff simply because it wants to be explicit, and there are people who care about that. Thus it's not that these "insane" things (for you, at least) are optimal. People would gladly improve them, if they could do that under the basic constraints that Rust wants to satisfy. The sad things about correctness is that we need to start from a bad, but correct, solution and try to improve it, while if correctness at all cost wasn't requires you could start from a much better, but not always correct, solution and have much less problems... until you do because nothing guaranteed you your program was correct. These are tradeoffs, and Rust choose a different one.

9 Likes

GUI is in fact an underdeveloped area of Rust development. But this is because GUI is fundamentally a large and difficult problem.

A large number of desktop apps nowadays use web technologies for their GUI via Electron. You can do the same with Tauri. You can get a declarative setup on top of a more native rendering stack with Slint. It doesn't have a WYSIWIG editor yet, but that's just a matter of Implementation. You can see more options at https://www.areweguiyet.com/

It works transparently in C# because C# is inserting locks/atomics for you. When you do StaticState.data = 0;, the C# runtime temporarily locks StaticState.data to ensure that the write happens safely. This doesn't have anything to do with lock statements except that I may actually use the same lock — I'm not sure.


Generally, rather than a free function with static state, in Rust if you absolutely want this design you'd have a global singleton with methods. That would look somewhat like

pub static SCRAPER: LazyLock<Mutex<Scraper>> = LazyLock::new(|| {
    Mutex::new(Scraper::default())
});

#[derive(Default)]
pub struct Scraper {
    state: Whatever,
}

impl Scraper {
    pub fn run(&mut self) { … }
}

// from wherever
SCRAPER.lock().unwrap().run();
// you can also make the mutex internal
// if you must do the locking implicitly

(LazyLock is unstable at the moment, but you can use the 3rd party Lazy which the design is based upon. Or you can make the construction const and skip the lazy cell.)

I'm sure you're aware of the singleton pattern from using C#. Using static data within a function is equivalent to using a singleton, with the same benefits and downsides.

The Rust language and standard library want you to be able to not elide these costs when you don't need them. This unfortunately does come at a cost of needing to ask for the costs when you do want them, or write abstractions which contain them.

6 Likes

Well, C# writes everything atomically in platform-word-sized chunks.

That's why you have to use Interlocked.Read Method (System.Threading) | Microsoft Learn to read a long or ulong reliably in a multi-threaded context in C#.

AKA C# does the equivalent of if you used Ordering::Relaxed atomics in Rust, and implemented u64 as two AtomicUsizes on 32-bit platforms.

That makes it not UB to have data races in C#, though of course it doesn't mean it'll do anything useful.

(And then there's some more stuff related to the .Net concurrent GC, the details of which I don't remember.)

5 Likes

I've scanned the GUI crates. I may find something useful there, but I'm not sure. It requires more time. The documentation for these crates is less than perfect. E.g., when you have followed the Quick Start in Tauri (the cargo version), you are left with a project folder where cargo run wouldn't work.

Concerning the state for my function fun(), I now intend to use the technique shown in the test program at the very top of this thread. It is more than adequate for my race-free environment. I'd definitely avoid more complexity unless adding some advanced mechanism can save me code in other places.

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.