An overlapping technique (covering some but not all of the circumstances you'd use assert) is to take a parse-dont-validate approach, and essentially encode the fact that an assertion has been applied to a value in its type.
How ergonomic this is will vary by language, but the general idea would be to apply the assertion logic in some sort of constructor, then prevent any operations which would break the invariant going forward. The simplest way to protect this being by making the value immutable where possible.
Users of the value who care about the invariant being true can then specify in their types that they want a non-empty-collection or a foo-id or whatever it may be, rather than asking for the wider type, then asserting.
It is so nice to come here and want to say something, and someone already said it.
For those who haven't read "Parse, Don't Validate": https://lexi-lambda.github.io/blog/2019/11/05/parse-don-t-va...
I'm not sure exactly what languages people think of when they consider asserting, but I presume it's Java, C# or C/C++. In Java asserts are disabled by default, so they're thought of as a debug/development utility.
In another thinkpiece, "It takes two to Contract", it is demonstrated how types and assertions work together in TigerBeetle: https://tigerbeetle.com/blog/2023-12-27-it-takes-two-to-cont...
I think people might be worried of asserting in production because "what if you hit an edge case in production that you haven't accounted for, and the system crashes?" And I either think "You just don't test enough", or "Parse, Don't Validate (rather than assert), report a problem and continue."
> Users of the value who care about the invariant being true can then specify in their types that they want
Now that C# has value types and Java has record classes, this kind of data modelling has become available in mainstream systems languages. I'm not a C++ shark, but I think the closest equivalent is C++20 aggregate structs.