I'm learning Rust and playing around with some small programs.
Here, I'm looking for any processes named "noita.exe". The program should exit if not exactly one process is found. My code works, but I have several points where I'm not sure that I do this in a good way.
use sysinfo::System;
fn main() {
let system = System::new_all();
let mut process_count = 0u8;
let mut noita_pid = 0;
for process in system.processes_by_exact_name("noita.exe") {
process_count += 1;
noita_pid = process.pid().as_u32();
println!("{} {}", process.pid(), process.name());
}
if process_count == 0 {
println!("No process 'noita.exe' was found. 🙁");
std::process::exit(1);
} else if process_count > 1 {
println!("More than one process 'noita.exe' was found. 😵💫");
std::process::exit(1);
}
}
I have not found a way to count the Iterator returned by processes_by_exact_name without calling the function twice, because .count() consumes the iterator and then I cannot access the Process inside. This required me to add an additional counter process_count, which I would preferred to have skippe entirely. Is there any way to achieve this?
Since I'm only interested in exactly one Process, I would like to have the noita_pid mutable. initialised in the for-loop. But since processes_by_exact_name() could also return nothing, this could potentially leave me with an uninitialised variable, wich is exactly the error I see.
In laymen's terms, if processes_by_exact_name() returns not exactly 1 Process, I want the application to exit. Otherwise, I want to store the process in an immutable variable. Logic tells me, what this should be safe to do, but I don't know how to do it in Rust.
The iterator that this method returns is not Clone, so your only other choices are to iterate twice or collect the results of iteration in a Vec and use Vec::len to get the count. This is a common situation and is not particular to Rust.
You can use an Option for this.
let mut noita_pid: Option<u32> = None;
And in the loop:
noita_pid = Some(process.pid().as_u32());
To check whether one was found:
let Some(pid) = noita_pid else {
// exit
}
if process_count > 1 {
// exit
}
// use pid
Or using the iterator directly, and avoiding the for loop and the counter, may be more appealing to you. This is more a matter of preference.
use sysinfo::System;
fn main() {
let system = System::new_all();
let mut iter = system.processes_by_exact_name("noita.exe");
let Some(noita_pid) = iter.next() else {
println!("No process 'noita.exe' was found. 🙁");
std::process::exit(1);
};
if iter.next().is_some() {
println!("More than one process 'noita.exe' was found. 😵💫");
std::process::exit(1);
}
// use pid
}
use itertools::Itertools;
let noita_pid = match system.processes_by_exact_name("noita.exe").exactly_one() {
Ok(process) => process.pid().as_u32(),
Err(not_one) if not_one.next().is_none() => {
println!("No process 'noita.exe' was found. 🙁");
std::process::exit(1);
}
Err(_) => {
println!("More than one process 'noita.exe' was found. 😵💫");
std::process::exit(1);
}
};
I like this. I assume this is normal Rust code, but it looks so weird for someone used to Python, C# or JS. Wow.
Edit:
Does not work:
no method named exactly_one found for opaque type impl Iterator<Item = &Process> + '_ in the current scope
method not found in `impl Iterator<Item = &Process>
Yes, but if the iterator is not fused, and it doesn't return Some on the first iteration, then your code doesn't find the desired element, so it's equally incorrect for a non-fused iterator, just in a different way.
Non-fused doesn't imply that it polls, or cycles, or anything else. It just means you have to read the doc to know what it does if you call next after a None.
Have you added itertools to your project and imported Itertools trait?
One big difference between Rust and many other languages (maybe even most other languages) is the fact that it's standard library is very small and most “convenience” methods come from external crares.
If you know any mainstream language (Python, C# or JS certainly qualify) then you know that their standard libraries are filled to the bring with obsoleete “don't use” APIs that couldn't ever be removed.
To avoid this Rust standard library only includes things that couldn't be implemented in third-party crates or that are used so often that it's hard to imagine any serious program which doesn't use them.
Many even consider inclusion of mpsc into std a mistake.
That's why the Rust book explains how to add crates literally in the very first non-trivial example that it discusses.
Actually it would. Even if non-fused iterator would return None, then Some the whole code would work because it still wouldn't match the requires “first one Some, then one None” pattern.
That being said, I still don't like that this code calls non-fused iterator twice after receiving None, but it's perfectly legal to do.
I do understand what you're saying, but based on prior discussions I've learned that unless an iterator is 1) fused, or 2) documented to return when you call next after a None, then you shouldn't call next after a None because it could panic. The documentation needs to be improved in this regard.
Therefore when it is not documented to return when calling next after None, the implication is that returning None means "no more". If this were not the case, many iterators wouldn't behave as normally expected, since many are not fused and do not document the next after None behavior; this particular iterator is one of those.
This probably goes without saying, but by fused I mean specifically that it implements FusedIterator in the type. For this iterator the type is the following, which doesn't have FusedIterator, so we can't depend on it being fused.
) -> impl Iterator<Item = &'a Process> + 'b
It's not a great situation. I only pointed it out in the first place because there is the danger of panic if you call next after None in this particular case, since that behavior isn't documented. Probably best to discuss further in the discussion thread I linked, or perhaps a new thread.
For reference here is an open issue that came out of the earlier discussion. And in fact this is probably the best place for more discussion.
Had to change it a bit to make it work. It was complaining about not_one being immutable:
let noita_pid = match system.processes_by_exact_name("noita.exe").exactly_one() {
Ok(process) => process.pid().as_u32(),
Err(not_one) => {
let mut not_one_iter = not_one; //
if not_one_iter.next().is_none() {
println!("No process 'noita.exe' was found. 🙁");
std::process::exit(1);
} else {
println!("More than one process 'noita.exe' was found. 😵💫");
std::process::exit(1);
}
}
};
If the iterator yields no elements, Ok(None) will be returned. If the iterator yields exactly one element, that element will be returned, otherwise an error will be returned containing an iterator that has the same output as the input iterator.
I implemented that like so:
let noita_pid = match system.processes_by_exact_name("noita.exe").at_most_one() {
Ok(process) => process.unwrap().pid().as_u32(),
Ok(None) => {
println!("No process 'noita.exe' was found. 🙁");
std::process::exit(1);
}
Err(_) => {
println!("More than one process 'noita.exe' was found. 😵💫");
std::process::exit(1);
}
};
But this warns about unreachable code at OK(None), and panics after running it, when I don't have a process "noita.exe" running.
thread 'main' panicked at src\main.rs:20:28:
called `Option::unwrap()` on a `None` value
When I swap the two OK(...) lines, everything works and the warning goes away. I don't suppose that there would be a way of making this work with the order above? It's not important, but I'd be curious to know.
Edit:
Got it. I had to do this instead: Ok(Some(process))
Yeah. That's the correct answer and as a bonus you got rid of unwrap. Which is looking very suspicious outside of tests (and locks because of certain decision which some consider a design mistake).
Yes, technically your code should should work, that's exactly what I meant.
However. It directly calls to external C code and in such cases I tend to be doubly cautios: C code is not very-well known for upholding fringe, non-trivial, yet required Rust invariants.
Rust introduced the notion of “non-fused” iterators to uphold one very important property: no UBs in code without unsafe.
However, in C it's much easier and simpler to implement not “correctly unfused” iterator (where you may call next as many times as you want to would get new results after first None) but “broken iterator” (iterate over std::map, return None if iterator points to std::end(map)… bam, done: now calls after first None may not just return Some, but would actually crash the program).
Yes, this is bug in such code, yes, proper Rust binding should turn it into fused iterator (it's easier than providing unfused one then allowing someone to make ir fused), yet… it happens.
Most often when original version had proper unfused iterator (e.g. if there's an array, iterator moves unsigned index and compares it to size) but then it was “optimized” with map (or other complex structure).
That's why I wrote:
P.S. Reminds me about old joke when aged parent ask one of newlyweds after h“ ”uge and explosive scandal where none of sides want to move an inch: “Do you want to be right… or happy?”. Similarly here: technically your code is correct and should works… but would it actually work? I'm not ready to bet anything on it.