The sys.monitoring and import optimisation suggestions apply as-is.
If you use standard unittest discovery the third item might apply as well, though probably not to the same degree.
I don’t think unittest has any support for distribution so the xdist stuff is a no.
On the other hand you could use unit test as the API with Pytest as your test runner. Then you can also use xdist. And eventually migrate to the Pytest test api because it’s so much better.
I wwsn't familiar with this sys.monitoring option for coverage. Going to give it a try in my test suite. At the moment with docker testcontainers, gh actions test matrix for multiple python versions, and unit + regression + integration tests it is taking about 3-5 minutes.
Warning, there is a change in coverage 7.7.0 that disables sysmon support for coverage if using branch coverage _and_ a version of Python before 3.14alpha6.
I'd dispute it being over-engineering: media keys tend to control a mix of hardware and software (OS) features (looking at asus keyboards on the internet I see audio volume, mic mute, fan speed / perf governor, display off, brightness, projection mode, touchpad off, sleep, and airplane mode).
Given this, an OS driver is a requirement, and the OS further needs to access the hardware for obvious reasons.
This means you can either implement everything uniformly in driver (just bouncing from the interrupt to a hardware operation in the case of hardware features), or you can mix the implementation between firmware and driver.
Unless you have a very good justification to do so (which I'd very much dispute the existence of for gaming-oriented ASUS laptops) the latter seems like the over-engineering.
> Rust, in many ways, is a terrible first systems programming language.
Contrariwise, Rust in, in many way, an awesome first systems programming language. Because it tells you and forces you to consider all the issues upfront.
For instance in 59nadir's example, what if the vector is a vector of heap-allocated objects, and the loop frees them? In Rust this makes essentially no difference, because at iteration you tell the compiler whether the vector is borrowed or moved and the rest of the lifecycle falls out of that regardless of what's in the vector: with a borrowing iteration, you simply could not free the contents. The vector generally works and is used the same whether its contents are copiable or not.
A lot of idiomatic systems code is intrinsically memory unsafe. The hardware owns direct references to objects in your address space and completely disregards the ownership semantics of your programming language. It is the same reason immediately destroying moved-from objects can be problematic: it isn’t sufficient to statically verify that the code no longer references that memory. Hardware can and sometimes does hold references to moved-from objects such that deferred destruction is required for correctness.
How is someone supposed to learn idiomatic systems programming in a language that struggles to express basic elements of systems programming? Having no GC is necessary but not sufficient to be a usable systems language but it feels like some in the Rust community are tacitly defining it that way. Being a systems programmer means being comfortable with handling ambiguous object ownership and lifetimes. Some performance and scalability engineering essentially requires this, regardless of the language you use.
None of these "issues" are systems issues, they're memory safety issues. If you think systems programming is about memory saftey, then you're demonstrating the problem.
Eg., some drivers cannot be memory safe, because memory is arranged outside of the driver to be picked up "at the right time, in the right place" and so on.
Statically-provable memory saftey is, ironically, quite a bad property to have for a systems programming language, as it prevents actually controlling the devices of the machine. This is, of course, why rust has "unsafe" and why anything actually systems-level is going to have a fair amount of it.
The operation of machine devices isnt memory safe -- memory saftey is a static property of a program's source code, that prevents describing the full behaviour of devices correctly.
Yes, touching hardware directly often requires memory unsafety. Rust allows that, but encourages you to come up with an abstraction that can be used safely and thereby minimize the amount of surface area which has to do unsafe things. You still have to manually assert / verify the correctness of that wrapper, obviously.
> This is, of course, why rust has "unsafe" and why anything actually systems-level is going to have a fair amount of it.
There are entire kernels written in Rust with less than 10% unsafe code. The standard library is less than 3% unsafe, last I checked. People overestimate how much "unsafe" is actually required and therefore they underestimate how much value Rust provides. Minimizing the amount of code doing unsafe things is good practice no matter what programming language you use, Rust just pushes hard in that direction.
> For instance in 59nadir's example, what if the vector is a vector of heap-allocated objects, and the loop frees them?
But the loop doesn't free them. This is trivial for us to see and honestly shouldn't be difficult for Rust to figure out either. Once you've adopted overwrought tools they should be designed to handle these types of issues, otherwise you're just shuffling an esoteric burden onto the user in a shape that doesn't match the code that was written.
With less complicated languages we take on the more general burden of making sure things make sense (pinky-promise, etc.) and that is one that we've signed up for, so we take care in the places that have actually been identified, but they need to be found manually; that's the tradeoff. The argument I'm making is that Rust really ought to be smarter about this, there is no real reason it shouldn't be able to understand what the loop does and treat the iteration portion accordingly, but it's difficult to make overcomplicated things because they are exactly that.
I doubt that most Rust users feel this lack of basic introspection as to what is happening in the loop makes sense once you actually ask them, and I'd bet money most of them feel that Rust ought to understand the loop (though in reading these posts I realize that there are actual humans that don't seem to understand the issue as well, when it's as simple as just reading the code in front of them and actually taking into account what it does).
The compiler isn't protecting them from anything in this particular example, we can all see this is valid code. It's provably valid code, just not provable by the Rust compiler.
Literally nothing in your comment is correct. In the Rust snippet vector is moved into the loop, and thus freed.
There are situations where you hit the limits of the borrow checker, plenty of them. This is not one of them. Again, the borrow checker is not even involved in the original snippet.
> In the Rust snippet vector is moved into the loop, and thus freed.
Do you see any code in the snippet that requires it to be? This could be a simple read-only borrow because the actual logic of the program requires only that, and the value could live on happily after the loop. Literally nothing in this snippet requires anything else, you've just sort of assumed that the way Rust does it is the only thing that makes sense.
It's not about what Rust currently does, really, it's about what it ought to do.
It’s important that a for loop takes ownership of the vec, because it’s the only way you can call things inside the loop body that require the element itself to be moved.
If you don’t want the loop to take ownership of the vec, there’s literally a one character change: put a & before the thing you’re iterating (ie. for x in &y). That borrows the vec instead of moving it.
You seem to want rust to decide for itself whether to borrow or own the contents, but that way lies madness… it will be really hard to reason about what more complicated code is doing, and changes would have very non-local effects on how the compiler decides to use your code.
For me, move-semantics-by-default is the key idea that rust got right, and it’s a very simple concept. It’s not intuitive, but it’s the key idea behind all of rust’s benefits for memory management and preventing concurrency bugs. “Learn one simple but non-intuitive thing and you get these huge benefits” is a tradeoff I’m very much willing to make, personally.
> You seem to want rust to decide for itself whether to borrow or own the contents, but that way lies madness…
Most of what Rust does already feels like madness, like the concept of implicit moves, etc., but I understand your point. I don't think the reasoning really makes sense in terms of actual logic, but as I wrote in another comment: It's possible that I've misunderstood the sales pitch of Rust trying to be GC-less GCd language.
> For me, move-semantics-by-default is the key idea that rust got right, and it’s a very simple concept. It’s not intuitive, but it’s the key idea behind all of rust’s benefits for memory management and preventing concurrency bugs. “Learn one simple but non-intuitive thing and you get these huge benefits” is a tradeoff I’m very much willing to make, personally.
I can respect that and seen this way (where we accept that we're simply going to have unintuitive and incorrect rejections of programs) it does make a lot more sense.
C++ “move” semantics are quite complicated. That said, those C++ semantics are much better at handling some edge cases in systems software that Rust largely pretends don’t exist. It is a tradeoff. C++ is much uglier but also much better at handling cases where ownership and lifetimes are intrinsically ambiguous in a moved-from context because hardware has implicit ownership exogenous to the code.
The equivalent of the C++ move in Rust are the function take/replace (like mem::replace ajd option::take).
And it is fully memory safe.
You can build all the ownership you want by using raw pointers in Rust. And there is nothing wrong with a specific problem requiring unsafe because the problem cannot be taught to the borrow checker. But there is a point in your stack of abstractions where you can expose a safe and ergonomic API.
If you have a concrete example I would love to get a crack at it.
> Remind me again how move, copy and clone works in C++ /s
Sarcasm, but it’s worth outlining… C++ “move semantics” are (1) precisely the opposite of rust, and (2) not move semantics at all.
- Rust doesn’t let you override what happens during a move, it’s just a memcpy
- C++ has an rvalue reference (&&) constructor, which lets you override how a thing is moved
- Rust doesn’t let you use the moved-from value
- C++ absolutely has no problem letting you used a value after wrapping it in std::move (which is really just a cast to an rvalue reference)
- Rust uses moves to allow simple memcpy’ing of values that track resources (heap space, etc) by simply making sure nobody can access the source, and not calling Drop on it.
- C++ requires you to write logic in your move constructor that “pillages” the moved-from value (for instance in std::string it has to set the source string’s pointer to nullptr and its length to 0.) This has the consequence of making the moved-from value still “valid”
For copies:
- Rust’s Copy is just “memcpy, but you can still use the original value”. Basically any type that doesn’t track some resource that gets freed on Drop. Rust simply doesn’t let you implement Copy for things that track other resources, like heap pointers.
- C++’s copy happens implicitly when you pass something by value, and you get to write arbitrary code to make it work (like copying a string will malloc a new place on the heap and copy the buffer over)
- Rust has an entirely different concept, Clone, which lets you write arbitrarily code to duplicate managed resources (analogous to how you’d use a C++ copy constructor)
- C++ has nothing to help you distinguish “deep copy that makes new copies of resources” from “dumb copy that is just a memcpy”… if your type has an expensive concept of deep copying, callers will (perhaps inadvertently) use it every time they pass your type by value.
IMO C++’s “move” still letting you touch the moved-from value is what made me realize how much C++ had lost the plot when C++11 came out. Rust’s semantics here are basically what happens when you look at what C++ was trying to do, and learn from its mistakes.
Are you suggesting Rust should automatically insert the borrow annotation because it is able to see that a borrow is sufficient? That would be quite unintuitive and make it ambiguous whether a for loop is borrowing or consuming the iterator without reviewing the body. I'd strongly argue that it should unambiguously do either one or the other and not try and read the author's mind.
Yes, I'm suggesting it should do the right thing for the code the loop is actually trying to execute. I personally think this is exactly what Rust and its users have signed up for. I might be mistaken about that, but I think it's in line with the more general view that Rust is attempting to be as close as it can get to a language that reads like it has a garbage collector without having one.
> the more general view that Rust is attempting to be as close as it can get to a language that reads like it has a garbage collector without having one.
I've used Rust a fair amount, and I've never seen that expressed as a goal.
A couple of general principles followed by Rust are to prefer explicit code over implicit conversions and to support local reasoning. Those are both present here: the borrow needs to be made explicitly, rather than implicitly based on code later on.
The code says to call into_iter and consume the iteratee, so rust does that. If you want a reference, use the &, just like in zig/c/c++/etc. You are saying an even more extreme version of "If there's a way what I wrote could possibly be interpreted that could compile, it should do that" ignoring the fact that there's almost assuredly _many_ ways that your code can be interpreted that could compile.
Slowing down type resolution/compilation (by making every unannotated loop a generic T|&T) and adding more syntax (since rust would need new annotations to explicitly specify borrow/take in for loops), in order to save a single character that matches the behavior of most other related languages and is perfectly clearly explained by the compiler, is maybe a bad move. Considering compile time and complicated syntax are two of the biggest things people who actually write rust complain about.
You mean the northwest like Galicia? The northeast where I live still gets plenty hot. We don't have major beach resorts like Lloret and Salou for no reason :)
Though I guess this might not qualify as "Spain" depending on who you ask - if I ask my neighbours if they are living in Spain they certainly will say NO :P Hot topic alert :)
The Atlantic Spain and everything around Picos de Europa. Also, most mountain ranges, and Spain is the 2nd most mountainous country in Europe, so a lot of Spain is not that warm in Winter.
That’s what lots of sites used to do in the late 90s and early aughts in order to have fixed elements.
It was really shit. Browser navigation cues disappear, minor errors will fuck up the entire thing by navigating fixed element frames instead of contents, design flexibility disappears (even as consistent styling requires more efforts), frames don’t content-size so will clip and show scroll bars all over, debugging is absolute ass, …
If you use standard unittest discovery the third item might apply as well, though probably not to the same degree.
I don’t think unittest has any support for distribution so the xdist stuff is a no.
On the other hand you could use unit test as the API with Pytest as your test runner. Then you can also use xdist. And eventually migrate to the Pytest test api because it’s so much better.
reply