It is common to have a type that uphold invariants and have a faillable constructor that can be a const fn. An example of this is NonZero.
When you need to create a value from constant values you know are valid you end up to have to use .unwrap(), which even if the compiler will optimize out the failling branch, feel "wrong" since using unwrap is generally a bad practise.
An alternative to this would be to wrap the expression in a const { } block, so that if unwrap would panic, it will panic at compile-time instead of runtime. But it makes the code more verbose.
let x = const { NonZero::new(5).unwrap() };
I propose to add a new syntactic sugar for this situation, something like the postfix operator ?.
Let use ! (bikeshedding).
I think this could be solved by pattern types. Essentially, it would work like this:
// Make NonZero field public
pub struct NonZero<T: ZeroablePrimitive>(pub T is ..0 | 1..);
// Then can use it like this
let x = NonZero(1);
// Or redefine `NonZero::new` (less likely)
impl<T: ZeroablePrimitive> NonZero<T> {
pub fn new(value: T)
-> Option<Self> is (Some if value is (..0 | 1..));
}
// Then can use it like this
let Some(x) = NonZero::new(1); // infallible pattern matching
I’d really like to see the language offer something in this space. I’ve used a lot of restricted numeric types (NonZero, NotNan…) and it’d be nice to have a uniform, concise syntax for fallible construction of constants. Each numeric type providing its own macro is possible, but macro_rules! can’t be directly exported from a crate’s modules except as reexports, which makes the public API clunky unless the number library goes to the length of having a helper library that defines the macro.
For prototyping, we can write a macro today on stable which approximates @tguichaoua’s semantics, for Result and Option but not other types:
(Obviously this isn’t very type-safe because it just invokes whatever the unwrap method is. But until we can use traits from const contexts, this is what we can reasonably do.) Then if this is combined with a plain function, it can be short if still rather punctuation-heavy:
const fn nz(x: u32) -> Option<NonZero<u32>> {
NonZero::new(x)
}
fn main() {
let x = k!(nz(3)).saturating_add(2);
println!("{x}");
}
Regarding what the language could offer, here’s another idea: what if ? was usable directly in const blocks, and what it branches to is a compile-time error (which is unambiguous because there is no enclosing function to return from)? That way, writing
let three = const { nz(3)? };
would have the desired effect. It’s not as concise as a dedicated syntax, but it introduces no new syntax at all — it’s a reasonable extension of existing elements, and sort of fits the precedent of giving ? a particular branch destination inside const {} is very much like giving it a particular branch destination inside try {} (unstable).
I don’t see what’s wrong with unwrap (or expect) in the first place. Sometimes you have more information than the compiler; this is true at compile time and at run time. I agree it’s reasonable to have a shorthand for converting literals (like the notzero crate or the hex-literal crate), but once you’re writing expressions, you might as well keep writing expressions.
I might stick this in my toolbox of useful macros alongside yeet!, ix!, whoops!, and impl_op!.
It's far from perfect, but you can write
#[doc(hidden)]
#[macro_export]
macro_rules! __my_macro { … }
/// Documentation for my_macro!
#[doc(inline)]
pub use __my_macro as my_macro;
to document a module scope macro item without the crate root item today. Rustdoc changed how hidden and inline interact to make this work a while back.
I believe oli-obk is gonna be working on "ranged integer types" with something like type NonZeroInt = i64 is ..0 | 1..;, but that will likely not be ready "soon".
I also think the const { <expr>? } syntax is preferrable, as <expr>! could lead to further abiguity:
const k: Option<[u8; 5]> = ...;
let x = k![3];
And more importantly, it establishes a hidden const block, which might confuse people, because just adding one symbol changes what you can do in the entire (previous) expression.