they do get attention, but generally people don't dwell on how unpredictable they were. Operation Warp Speed got covid vaccines out within ~1 year. Initial predictions were more in the >= 5 years time range. Fast covid vaccines were very much a "white swan" event, but reporting around it wasn't "this is so unlikely to have worked out" etc.
"people with disabilities that make it impossible for them to succeed in normal school" is not a clearly divisible population from the regular student population though. Many (but not all) districts deal with disabilities via IEPs, or Individual Education Plans. They are tailored to particular students, and can be fairly common. They make things less of a clear binary than 2 separate school systems would really need.
It's worse because there's been a trend among elite districts to push students to (fraudulently) get a diagnosed disability, so that they can get accommodations on tests and raise their chances to be admitted to an elite university. So, a proposal to partition the school system into a lesser system for students with disabilities would face pushback by the aforementioned elite district parents. While they are participating in a fraud (and so it would perhaps be morally fine for them to face repercussions for it), I imagine it would make implementing any such plan very difficult.
Yep, the abuse is happening over here in slovenia too, you get some diagnosis for the kid, you get 50% more test-taking time, extra help in school, extra accomodations for other stuff, and in the end, your grade is worth the same (for grade averages and high school or college acceptance) as someone elses who finished in regular amount of time. No remarks anywhere saying "while student A and B have the same point average, student B had 50% more time on the test".
So yeah, I kinda understand why parents get the diagnoses for their kids, but the system is unfair.
Giving accomodations during a test kind of invalidates the test as a measurement of relative ability, or aptitude for further studies, so perhaps the solution is to stop doing that
hard to give recommendations without more information. For example
1. what languages does he know? there are boardgames that are localized into other languages. Probably the easiest route tbh.
2. what kinds of games does he like? for example, many boardgames have very little english on the game pieces. think any game that uses a standard poker deck, e.g. solitaire, or many others. Mahjong is another example though, as is dominos).
There are some modern boardgames that might also be fine, namely ones that discourage communication in the first place. It's common in co-op boardgames. For example, the Lord of the Rings trick taking game is 1-4 players, and during gameplay there is no discussion allowed. Game pieces can be separated into two categories
1. scenario-specific ones, which have text on them/must be read to be understood/played. You could maybe translate them? or it may have been localized for a language he's literate in. I don't know.
2. secnario-independent ones, which are (functionally) poker cards.
For this game you only need to share language when understanding the scenario-specific cards, and when planning strategy before each scenario starts. I would be comfortable playing the game with someone I don't share a language with if
1. we both know the game (this would be the hard part), and
2. we have two copies of the game, so we each can read our scenario-specific cards in our own language, and
3. we struggled through with a translation app before each scenario starts, if we want to discuss strategy.
Many boardgames are also available in spanish. BoardGameGeek is a popular boardgame website. Unfortunately, they don't seem to have an ergonomic way to filter searches on a spanish-language version of the game being available. But you can go to a particular game, look at the "versions" tab, and see what languages are available. From this I can see the LOTR trick taking game I mentioned is published in spanish (as well as many other languages).
I don't know firsthand for good games similar to what you've mentioned. I asked an LLM about "enhanced" versions of Solitaire specifically and is mentioned
* The Crew: it's a trick-taking game (like the LOTR one). which of the two trick taking games is better is probably debatable, notably the crew is 2-5 players, while LOTR is 1-4. I've only played LOTR at >= 2, so can't comment on it at 1 player.
* Regicide: 1-4 person cooperative game where you "fight monsters" by playing standard poker cards. I haven't played it personally but I saw a video on it and was interested in it from that.
All of the above (I think) have their core game pieces not really involve any language (besides eg numbers). So you could get them in spanish, and still plausibly play with them if you learned the english rules from somewhere. BoardGameGeek often has rules files available online, e.g. regicide's rules in english are downloadable on this page
for context: in rust the "translation unit" is a crate (in C++, each source file is its own translation unit). So you only get parallelism across crates when compiling in rust. When you have a single-crate project, this means that
1. you get parallelism across all your dependencies, but
2. your (final) crate is serial.
Splitting your crate can therefore get you parallelism back in step 2, which can be a (compilation) perf win. You can also get a caching benefit when there aren't changes, but even absent that you can get wins.
It can come at the cost of runtime performance though, as rust won't (by default) do things like inline across translation units iirc. You can get back some of this perf back via various tricks (for example link-time optimization).
Looks like the rust performance book has some writing on these tradeoffs. I haven't read these articles, but the table of contents on the left seems reasonable enough.
Again I haven't read that (so I'm assuming it says the following), but you can get "best of both worlds" by configuring debug builds to not do LTO (= faster compilation speed, slower runtime), and release builds to do LTO (= faster runtime, slower compilation speed). There are also variants of LTO that make various tradeoffs you could look into.
That article also links to the `cargo-wizard` subcommand of cargo
it won't auto-split crates for you (of course), but it does seem to give you some default configurations of `cargo`, one of which configures for faster compilation speed. could be an easy way to mess around with things.
Thanks, that's helpful to keep in mind. Looks like you can fiddle with organizing the project in way where you can balance 1 and 2 in a reasonable way. Right now, it's definitely the second step that's killing me where assembling the crates together takes forever.
you can try doing `cargo build --timings ...`. It will generate a report of how long each crate takes to compile etc. It sounds like you know that your final crate is the culprit, but this would let you confirm it.
If you suspect the issue is assembling all the files together (e.g. linking) you can see some advice on optimizing link speeds here
Note that there could be other causes of slow compilation of the final binary. For example, if you heavily use (especially procedural) macros, it's known to make compilation quite slow.
if you have an OOM and want to log why, if your logging allocates it will likely fail as well. You could in principle work around this with enough effort, but "properly" handling OOM is typically much more trouble than its worth.
There's a number of things about rust that help compared to other statically typed languages.
1. the compiler gives very high quality error messages. It helps humans, and also helps LLMs
2. Rust reduces memory management to local reasoning (via the borrow checker). This means that it performs well even as context grows, because checks in one function/module are well-encapsulated to that function/module.
3. Rust can more easily obtain this encapsulation for more general properties than many other statically typed languages. In particular, rust's type system is very strong, so it's easy to take a function `func(x: T)` that relies on some implicit assumption on `x` (say that it is non-zero), and turn it into an explicit requirement. By this, I mean you define `pub struct NonZero(T)`, and provide constructors `pub try_new(t: T) -> Result<NonZero<T>, _>` that error if the condition doesn't hold. If you additionally only provide public methods on `NonZero<T>` that uphold the invariant, you can lift runtime runtime assertions to the type level. This is both good practice, and helps out LLMs quite a bit.
This is to say that rust makes it quite easy to encapsulate implementation details (both regarding memory management, as well as other details) essentially completely. Sometimes you still have invariants that need care/can't be encapsulated in the type system, but such invariants should be marked `unsafe`, so it can be easier to audit the LLM's output.
Anyway, the "more constraints to balance" is only problematic if all the constraints are inter-dependent. It's definitely possible to get LLMs to generate spaghetti code like this, but the way you fix it is the way you fix similar issues in other languages.
> This is both good practice, and helps out LLMs quite a bit.
Don’t get me wrong, I like this aspect of Rust, but I can’t make heads or tails as to whether it helps or if they just have to iterate more to figure out how to make something work. LLMs already do pretty well with a comment “this value can’t be zero” in my experience, so I’m unsure how much value the static typing provides. Maybe it lets you get by with a lower quality model, but that model will likely just spend more tokens on iteration so I can’t discern an obvious win. (shrug) I hope I’m wrong though—if I can have super fast code with the ease of LLM generation then I’m happy.
> Rust reduces memory management to local reasoning (via the borrow checker). This means that it performs well even as context grows, because checks in one function/module are well-encapsulated to that function/module.
I don’t think this is true, right? Changing a single lifetime in a function signature can easily propagate across your entire program. Maybe I’m just a Rust noob, but any time I change a field from owned to borrowed or vice versa I have to propagate that change pretty broadly, which to my mind implies consuming a lot of the context window. Garbage collection (I know, ewww, shame on me, etc) allows for local reasoning in a much more meaningful way however morally impure it may be. :)
LLMs do significantly better when they get reliable feedback on their actions (try to create any non-trivial project in some language without letting the LLM use a compiler. Similarly, talking with a "chat LLM" will produce worse code than an "agentic LLM").
Anyway, making such a (breaking) change in rust immediately tells you all of the callsites that break. You have to chase it through, but that's mechanical/low context work. More formally, you can parallelize across files with sub-agents to not pollute your main agents context window. So it really should be a "zero context window" cost.
Whether or not strict typing is strictly better is really a correctness/velocity tradeoff, the same as it always has been. For most projects something in the middle is right.
As for owned vs borrowed and things propagating quite a bit, sometimes it happens. It's often avoidable with a couple of tricks
1. always default to borrowed unless you have a good reason to otherwise
2. make your function signatures more permissive so they can support either way. This can be done by modifying f<T>(x: T) (or f<T>(x: &T)) to f<U: AsRef<T>>(x: U). The later can be equivalently written as f(x: impl AsRef<T>).
When you say "change a field from owned to borrowed", I'd generally suggest not doing that. It's generally easier to start with some owned type MyType. You can then have function signatures take &MyType as input. This borrows all the fields, and is often good enough for most functions.
If you have a more esoteric function (that needs a combination of borrowed and owned inputs), it's typically easier to define a struct for that function. The steps are
1. Define a FunctionInputsRef<'a>
2. write `impl<'a> From<&'a MyStruct> for FunctionInputsRef<'a>`, then
3. update your function to take as input FunctionInputsRef<'a> rather than `&MyStruct`, and
4. update callers with `input -> input.into()`.
It has the benefit of less churn, as you're maintaining the old def (which might be useful elsewhere), and only updating the callers in a fairly trivial way. `FunctionInputsRef<'a>` can also be defined local to the function, so it is modularized better. If you later have other functions with other requirements, it's a relatively easy pattern to duplicate as well.
1. thiserror just does codegen of the "standard" enum things people do. if you find debugging thiserror difficult, just write out the enums manually. sure it's uglier, but (roughly) equivalent. so its preferable as synctatic sugar for enums, but doesn't have any technical benefits (in the same way that syntatic sugar never really does).
2. for boxed errors, you only get allocations on your error path. Hopefully this is a cold path so it shouldn't matter.
There is a general theme behind rust error handling though which it can be good to internalize. In particular, the more details of your errors you encode in the type system, the more powerful things are. Any error type could just be
pub struct MyError(String)
the issue is that this gives very little information to a caller on what to do with your error. If you have no callers (e.g. are making a binary) it's fine, and (roughly) what `anyhow` does.
That all being said, when designing errors a natural question to ask is "can my caller do anything meaningful with this error"? For example, in Rust stdlib, `Vec::push` can allocate. This allocation can fail, which panics. "Proper" error handling would use the fallible allocation API, and propagating an OOM error or whatever through results. For most applications, this is not an error that is worth investing that much time into guarding against, so using the (potentially panicing) `Vector::push` makes things easier.
You can take this same perspective in other settings as well, in particular separating out errors into
1. structured data, that a caller should be able to extract/process to handle the error, and
2. unstructured data, that is more used for logging, and you expect the caller to pass up the call stack without inspecting themself.
Handling both types of these errors with `thiserror` can be tedious for little benefit. I've found it useful to instead solely use `thiserror` for category 1, and category 2 does other things. This could be using `.expect(...)`. There are some crates that make this nicer (e.g. `error_stack`). But the point is that it can significantly clean things up if you only encode in your error enums failures that you expect someone to handle, rather than just e.g. log.
This does somewhat validate your point that the "right way" I've been experimenting with (and mentioned above) is not just "use this-error".
Also: a big issue with `thiserror` is the tedium of handling the large error enums (or giving up on using it "properly" and shoving together multiple error variants in some unstructured error type). that is somewhat better in the LLM era, as you can have the LLM handle the tedium.
also thiserror isn't incompatible with io::Error? all thiserror does is do code generation for "typical" enum errors. The errors it generates are normal rust code though, and the fact that they're generated by thiserror shouldn't really matter?
I don't get where the idea comes from that the popular error crates make error handling complicated in Rust. Because you're right, all thiserror is doing is giving you a shorter syntax for writing error enums. You could write the exact same things out by hand if you wanted to, and from the library user's side nothing would change.
As for anyhow, if a library ever exposes that, then that's just the author being lazy and not doing errors correctly. It's the equivalent of doing throw Exception("error!") in C#.
circular breathing is useful for other instruments as well, though it's not typically a technique that's necessary until you get to fairly high levels.
reply