Snapshots diff current with previous output and I only have to accept or reject the diff. I don't have to write the expected response myself. Snapshots can also stub out parts of the response that are not determistic.
> Snapshots can also stub out parts of the response that are not determistic.
TIL! The way I knew to do it was to have a mock implementation that behaved like the real thing, expect for data/time/uuids/..., where there was just a placeholder.
Snapshot tests being able to "mask" those non-deterministic parts sounds cool!
tbh, that seems pretty close to what I would call snapshot testing already. What people usually do with it is using it for more broadly compared to API testing (for example, I currently use it to test snapshots of a TUI application I am developing) - i.e. you can use it whenever your test is of the form "I have something that I can print in some way and that should look the same until I explicitely want it to look differently in the future". There are a bit more bells and wizzles - For example, it is nice that one does not have to write the initial snapshots oneself. You write the test, execute, it creates the resulting file, then you review and commit that - handy little workflow!
Rewriting could also make sense if there is a chance to improve the architecture based on the experience with the existing codebase. Something that would otherwise be impossible to even consider.
where do you even base these claims? Do you know what C# and Java threads have that Rust doesn't? data races. And don't get me stated on the biggest paradigm failure that is OOP.
Projects I've seen at work. Projects posted on Hacker News. Data races aren't usually an issue for backend services, and modern Java/C# is multi-paradigm.
> Data races aren't usually an issue for backend services
I beg to differ unless all your logic is in the database with strong isolation guarantees.
Speaking of C# for backends that are using EF actively, I bet there are bugs in pretty much all of them caused by incorrect applications of optimistic concurrency.
There are domains where C# (and F#) productivity stems from similar reasons why writing a game in something that isn't Rust might be more productive without even sacrificing performance (or, at least, not to the drastic extent).
I can give you an example:
var number = 0;
var delay = Task.Delay(1000);
for (var i = 0; i < 10; i++)
{
Task.Run(() =>
{
while (!delay.IsCompleted)
{
Interlocked.Increment(ref number);
}
});
}
await delay;
How would you write this idiomatically in Rust without using unsafe?
To avoid misunderstanding, I think Rust is a great language and even if you are a C# developer who does not plan to actively use it, learning Rust is of great benefit still because it forces you to tackle the concepts that implicitly underpin C#/F# in an explicit way.
There's a few things here that make this hard in Rust:
First, the main controller may panic and die, leaving all those tasks still running; while they run, they still access the two local variables, `number` and `delay`, which are now out of scope. My best understanding is that this doesn't result in undefined behavior in C#, but it's going to be some sort of crash with unpredictable results.
I think the expectation is that tasks use all cores, so the tasks also have to be essentially Send + 'static, which kinda complicates everything in Rust. Some sort of scoped spawning would help, but that doesn't seem to be part of core Tokio.
In C#, the number variable is a simple integer, and while updating it is done safely, there's nothing that forces the programmer to use Interlocked.Read or anything like that. So the value is going to be potentially stale. In Rust, it has to be declared atomic at the start.
Despite the `await delay`, there's nothing that awaits the tasks to finish; that counter is going to continue incrementing for a while even after `await delay`, and if its value is fetched multiple times in the main task, it's going to give different results.
In C#, the increment is done in Acquire-Release mode. Given nothing waits for tasks to complete, perhaps I'd be happy with Relaxed increments and reads.
So in conclusion: I agree, but I think you're arguing against Async Rust, rather than Rust. If so, that's fair. It's pretty much universally agreed that Async Rust is difficult and not very ergonomic right now.
On the other hand, I'm happy Rust forced me to go through the issues, and now I understand the potential pitfalls and performance implications a C#-like solution would have.
Does this lead to the decision fatigue you mention in another sub-thread? It seems like it would, so I'll give you that.
For posterity, here's the Rust version I arrived at:
let number = Arc::new(AtomicUsize::new(0));
let finished = Arc::new(AtomicBool::new(false));
let finished_clone = Arc::clone(&finished);
let delay = task::spawn(async move {
sleep(Duration::from_secs(1)).await;
finished_clone.store(true, Ordering::Release);
});
for _ in 0..10 {
let number_clone = Arc::clone(&number);
let finished_clone = Arc::clone(&finished);
task::spawn(async move {
while !finished_clone.load(Ordering::Acquire) {
number_clone.fetch_add(1, Ordering::SeqCst);
task::yield_now().await;
}
});
}
delay.await.unwrap();
use std::{
sync::{
Arc,
atomic::{AtomicBool, AtomicUsize, Ordering},
},
time::Duration,
};
fn main() {
let num = Arc::new(AtomicUsize::new(0));
let finished = Arc::new(AtomicBool::new(false));
for _ in 0..10 {
std::thread::spawn({
let num = num.clone();
let finished = finished.clone();
move || {
while !finished.load(Ordering::SeqCst) {
num.fetch_add(1, Ordering::SeqCst);
}
}
});
}
std::thread::sleep(Duration::from_millis(1000));
finished.store(true, Ordering::SeqCst);
}
What if we want to avoid explicitly spawning threads and blocking the current one every time we do this? Task.Run does not create a new thread besides those that are already in the threadpool (which can auto-scale, sure, but you get the idea, assuming the use of Tokio here).
I was implying that yes, while it is doable, it comes at 5x cognitive cost because of micromanagement it requires. This is somewhat doctored example but the "decision fatigue" that comes with writing Rust is very real. You write C# code, like in the example above, quickly without having to ponder on how you should approach it and move on to other parts of the application while in Rust there's a good chance you will be forced to deal with it in a much stricter way. It's less so of an issue in regular code but the moment you touch async - something that .NET's task and state machine abstractions solve on your behalf you will be forced to deal with by hand. This is, obviously, a tradeoff. There is no way for .NET to use async to implement bare metal cooperative multi-tasking, while it is very real and highly impressive ability of Rust. But you don't always need that, and C# offers an ability to compete with Rust and C++ in performance in critical paths when you need to sit down and optimize it unmatched by other languages of "similar" class (e.g. Java, Go). At the end of the day, both languages have domains they are strong at. C# suffers from design decisions that it cannot walk back and subpar developer culture (and poor program architecture preferences), Rust suffers from being abrasive in some scenarios and overly ceremonious in others. But other than that both provide excellent sets of tradeoffs. In 2025, we're spoiled with choice when it comes to performant memory-safe programming languages.
To be honest this sounds like something someone inexperienced would do in any language.
If you're not comfortable in a language, then sure you ponder and pontificate and wonder about what the right approach is, but if you're experienced and familiar then you just do it plain and simple.
What you're describing is not at all a language issue, it's an issue of familiarity and competency.
It's literally not 5x the cost, it would take me 3 minutes to whip up a tokio example. I've done both. I like C# too, I totally understand why you like it so much. This is not a C# vs Rust argument for me. All I'm saying is that Rust is a productive language.
Rust is manual by design because people need to micro-manage resources. If you are experienced in it, it still takes a very little time to code your scenario.
Obviously if you don't like the manual-ness of Rust, just use something else. For what you described I'd reach for Elixir or Golang.
I was disagreeing with you that it's not easy or too difficult. Rust just takes a bit of effort and ramping up to get good at. I hated it as well to begin with.
But again -- niches. Rust serves its niche extremely well. For other niches there are other languages and runtimes.
- I recommend reading the comment history of @neonsunset. He has shared quite some insights, snippets and benchmarks to make the case that if you do not need the absolute bare metal control C or Rust provides, you are better of with either .net or Java.
- Whereas in .net you have the best native interop imaginable for a high level language with a vast SDK. I understood that Java has improved on JNI, but I am not sure how well that compares.
- Programming languages are like a religion, highly inflammable, so I can imagine you would not be swayed by some rando on the internet. I would already be happy if you choose Go over Python, as with the former you win some type safety (but still have a weak type system) and have a good package manager and deployment story.
- Go was designed for Google, to prevent their college grads from implementing bad abstractions. But good abstractions are valuable. A weak type system isn't a great idea (opinion, but reasonable opinion). Back then .net was not really open source (I believe) and not as slim and fast as it is now, and even then, I think Google wants to have control about their own language for their own internal needs.
- Therefore, if you are not Google, Go should likely not be your top pick. Limited area of application, regrettable decisions, tailored for Google.
> if you do not need the absolute bare metal control C or Rust provides, you are better of with either .net or Java.
That, like your next point, is a relatively fair statement but it's prone to filter bubble bias as I am sure you are aware. I for example have extracted much more value out of Golang... and I had 9 years with Java, back in the EJB years.
Java and .NET are both VM-based and have noticeable startup time. As such, they are best suited for servers and not for tooling. Golang and Rust (and Zig, and D, V and many other compiled languages) are much better in those areas.
> Programming languages are like a religion, highly inflammable, so I can imagine you would not be swayed by some rando on the internet
Those years are long past me. I form my own opinions and I have enough experience to be able to make fairly accurate assessments with minimum information.
> I would already be happy if you choose Go over Python, as with the former you win some type safety (but still have a weak type system) and have a good package manager and deployment story.
I do exactly that. In fact I am a prominent Python hater. Its fans have gone to admirable heights in their striving to fill the gaps but I wonder will they one day realize this is unnecessary and just go where those gaps don't exist. Maybe never?
And yeah I use Golang for my own purposes. Even thinking of authoring my own bespoke sync and backup solution stepping on Syncthing, GIT and SSH+rsync and package it in Golang. Shell scripts become unpredictable from one scale and on.
> Go was designed for Google, to prevent their college grads from implementing bad abstractions. But good abstractions are valuable. A weak type system isn't a great idea (opinion, but reasonable opinion). Back then .net was not really open source (I believe) and not as slim and fast as it is now, and even then, I think Google wants to have control about their own language for their own internal needs.
That and your next point I fully agree with. That being said, Golang is good enough and I believe many of us here preach "don't let perfect be the enemy of good". And Golang symbolizes exactly that IMO; it's good enough but when your requirements start shooting up then it becomes mediocre. I have stumbled upon Golang's limitations, fairly quickly sadly, that's why I am confining it to certain kinds of projects only (and personal tinkering).
> I recommend reading the comment history of @neonsunset.
I don't mind doing that (per se) but I find appeals to authority and credentialism a bit irritating, I admit.
Plus, his reply was in my eyes a fairly low-effort snark.
> Java and .NET are both VM-based and have noticeable startup time. As such, they are best suited for servers and not for tooling. Golang and Rust (and Zig, and D, V and many other compiled languages) are much better in those areas.
For JIT-based deployments, it is measured in 100-500ms depending on the size of application, sometimes below. .NET has first-party support for NativeAOT deployment mode for a variety workloads: web servers, CLI tools, GUI applications and more.
Go is a VM-based language, where VM provides facilities such as virtual threading with goroutines (which is higher level of abstraction than .NET's execution model), GC, reflection, special handling for FFI. Identical to what .NET does. I don't think the cost and performance profile of BEAM needs additional commentary :)
Go also has weaker GC and compiler implementations and, on optimized code, cannot reach the performance grade of C++ and Rust, something C# can do.
> Those years are long past me. I form my own opinions and I have enough experience to be able to make fairly accurate assessments with minimum information.
The comments under your profile seem to suggest the opposite. Perhaps "minimum information" is impeding fair judgement?
> I don't mind doing that (per se) but I find appeals to authority and credentialism a bit irritating, I admit.
Is there a comment you have in mind which you think is engaging in credentialism?
> Is there a comment you have in mind which you think is engaging in credentialism?
The other guy who told me to inspect your profile. Not you.
> The comments under your profile seem to suggest the opposite.
Sigh. You seem to act in bad faith which immediately loses my interest.
You'll have to take your high and mighty attitude to somebody else. Seems like even HN is not immune from... certain tropes, shall we call them, generously.
(I would note that EJB is something from the past. Like .net has also really grown.)
> that's why I am confining it to certain kinds of projects only (and personal
tinkering).
You have a fair view of Go, I think. I could see that it makes sense to use it as a replacement for bash scripts, especially if you know the language well.
Personally I am wanting to dive into using F# for my shell scripting needs. The language leans well into those kind of applications with the pipe operator.
If you ever have the appetite, you should take a look at it as it can be run in interpreted/REPL mode too, which is a nice bonus for quick one-of scripts.
Even when it's used by mediocre developers, which is probably more than 90% of us, myself very much included? All I've been seeing is Rust being used by very enthusiastic and/or talented developers, who will be productive in any language.
If your baseline is a language that is missing some features that were in standard ML, sure. If you were already using OCaml or F#, Rust doesn't make you any more productive. If you were already using Haskell or Scala, Rust's lack of HKT will actively slow you down.
Longer compile times prolong iterative programming processes and having to take care of memory management through linear types adds restrictions on how you can express code and leads to additional mental burden. This can be part of a trade-off, where you get things in return (no runtime/gc), but your typical web app I don't see much advantages.
Agreed, RustRover is by far the best IDE for Rust atm. I also use the AI Assistant which is so toned down that it's actually useful and not full of spam.
They're great! But also still miss fundamental pieces that Nix gets right. Especially the way the build and dependency graph goes right to the root of every package, and comes together to completely describe the system you're running today.