As a result for example std::optional<&T> doesn't exist, because to a C++ programmer it seems as though this might have assign-through semantics (!) and so WG21 decided to kick this can down the road. C++ 26 might get std::optional<&T>
The main concern with that component was ensuring we can allocate stack storage for an object that may or may not be initialized.
The reference is easily achievable by using T* so is of minimal value, but also poses some more semantic problems since a reference is not copyable while an optional is.
For me the important use case is pattern matching, which C++ doesn't yet have. Pattern matching really changes how you see the entire language.
Here is the first example I found on Google if that helps you understand.
std::variant<Fluid, LightItem, HeavyItem, FragileItem> package;
std::visit(overload{
[](Fluid& ) { std::cout << "fluid\n"; },
[](LightItem& ) { std::cout << "light item\n"; },
[](HeavyItem& ) { std::cout << "heavy item\n"; },
[](FragileItem& ) { std::cout << "fragile\n"; }
}, package); match (state, pipe) {
(State::None, Pipe::Ground) => {
if inside {
n += 1;
}
}
(State::None, Pipe::Vert) => {
inside = !inside;
}
(State::None, Pipe::Se) => {
state = State::South;
}
(State::None, Pipe::Ne) => {
state = State::North;
}
// Horizontal lines make no difference to anything
(State::North | State::South, Pipe::Horiz) => {}
// U-turns
(State::South, Pipe::Sw) | (State::North, Pipe::Nw) => {
state = State::None;
}
// Form a vertical line
(State::South, Pipe::Nw) | (State::North, Pipe::Sw) => {
inside = !inside;
state = State::None;
}
_ => {
panic!("Unexpected sequence {state:?} {pipe:?}");
}
}Guess what, the same syntax I gave supports exactly that as well.
I haven't checked recent C++ standards, but I don't believe you can use partial classes/extensions in C++ like some other OO languages to add these methods to a native type. Many helper functions commonly used in Rust also only seem to exist in C++23, which not ever project can be compiled under yet.
In normal C++ code, the native types would probably be better to use, but if you're going full Rust style code, you may as well use these new types.
Isn't that value()?
So that says it's a method (its first parameter is the type itself, but named self rather than as a normal parameter so we can use method syntax instead of calling the function Option::expect) but it also takes an immutable reference to a string slice.
That second parameter, msg, is the text for a diagnostic if/ when you're wrong.
So, in a sense it's like value() but the diagnostic text means, when I was wrong...
let goose = found.expect("Our goose finder should always find a goose");
... I get a diagnostic saying that the problem is with "Our goose finder should always find a goose". Huh. I think we know where to start trouble shooting.In your example, it's likely that the person who sees this message won't have enough context to understand it; it's more like a debugging assert. Since you'll need a debugger and a breakpoint anyway, the message isn't very helpful.
In most cases then, if you don't know this code very well, that's fine because it's not your bug. In the edge case that you just got handed a pile of poorly documented code somebody else wrote, perhaps over several years, well, at least you know what they thought is supposed to happen here and that they're wrong.
And no, I don't find it better to be told "It broke, break out a debugger and try to reproduce the fault". With this text we can revisit the Goose wrangling code and maybe, now that we're staring at it knowing a real customer saw this fault, we are inspired and realise that sometimes it won't find a Goose, then decide what to do about that.
I think it's pretty odd to use a quick example someone rattled off on a web forum to explain a function's behaviour as evidence of its usefulness or lack thereof, as if the only thing a person could possibly write in a freeform error message is "Our goose finder should always find a goose".
Now I appreciate a clear explanation for an uncommon assert and for example, OpenCV could do with more of those, but in most functions, seeing the line that throws the error is enough to understand.
On the other hand expect will provoke the message you wrote if it fails. Of course if it's inside a consumer's fitness tracker it probably doesn't have any way to show the message to a human, but that's a different problem - the fitness tracker presumably can't display stack traces either.
Unlike C++, Rust doesn't support throwing exceptions, so expect() failing would panic. By default, this means dumping a stack trace and terminating the program, and the message provided in "expect" would be printed right before the stack trace.
For example:
fn main() {
let x: Option<i32> = None;
x.expect("Oh no!");
}
will print: thread 'main' panicked at src/main.rs:3:7:
Oh no!
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace
With RUST_BACKTRACE set to "full", it'll print: $ RUST_BACKTRACE=full ./target/release/demo
thread 'main' panicked at src/main.rs:3:7:
Oh no!
stack backtrace:
0: 0x5ff43befa755 - <std::sys_common::backtrace::_print::DisplayBacktrace as core::fmt::Display>::fmt::ha52e99bffe3c0898
1: 0x5ff43bf1769b - core::fmt::write::h5fdd5156f2480a24
2: 0x5ff43bef8a5f - std::io::Write::write_fmt::ha2c0b019f448d2c3
3: 0x5ff43befa52e - std::sys_common::backtrace::print::he84813a4ed1c2825
4: 0x5ff43befb7e9 - std::panicking::default_hook::{{closure}}::h033521c27c9929b1
5: 0x5ff43befb52d - std::panicking::default_hook::had42987aad9de78c
6: 0x5ff43befbc83 - std::panicking::rust_panic_with_hook::h80fc1b429f5a5699
7: 0x5ff43befbb64 - std::panicking::begin_panic_handler::{{closure}}::h5aa7b89233b1ae33
8: 0x5ff43befac19 - std::sys_common::backtrace::__rust_end_short_backtrace::h0e4c5e6cee7f8a24
9: 0x5ff43befb897 - rust_begin_unwind
10: 0x5ff43bee0b63 - core::panicking::panic_fmt::h3bea7be9b6a41ace
11: 0x5ff43bf16c6c - core::panicking::panic_display::h20da06138ce63f85
12: 0x5ff43bee0b2c - core::option::expect_failed::h92448d4f1092eaaa
13: 0x5ff43bee127a - demo::main::ha244b8f1ce6eaa44
14: 0x5ff43bee1223 - std::sys_common::backtrace::__rust_begin_short_backtrace::hfc5c93265480da58
15: 0x5ff43bee1239 - std::rt::lang_start::{{closure}}::h988fdfb65ef3da3b
16: 0x5ff43bef6be6 - std::rt::lang_start_internal::h64c4082ce77a6bd6
17: 0x5ff43bee12a5 - main
18: 0x741d2a628150 - __libc_start_call_main
at ./csu/../sysdeps/nptl/libc_start_call_main.h: 58:16
19: 0x741d2a628209 - __libc_start_main_impl
at ./csu/../csu/libc-start.c:360:3
20: 0x5ff43bee1155 - _start
21: 0x0 - <unknown>
Whether you would want to replicate this behaviour in C++, I don't know; I find panic!() to be quite destructive, and catastrophic when it's used in libraries or frameworks. I think the C++ implementation just throws an exception, but Rust's .expect() does not behave like .value() in C++.Implicit conversion that immolates: imolicit conversion.
In this world, as the document you've linked says: "The behavior is undefined if *this does not contain a value."
The operators for such access are actually `noexcept` - the exception you're apparently relying on would be illegal.
Once in a while I go down a rabbit hole, but hey, it's not as though HN isn't a rabbit hole anyway.
Of course not, they’re literally `noexcept`, what they do is UB if empty.
value() will throw.
This feels like an easy choice in isolation, but at the time this was being developed (and arguably even now), there’s no definitive plan to holistically move C++ code to being safe by default. So whenever that happens, a ton of things will need to be dealt with, and there’s always the possibility that being an odd API here makes that overall move harder not easier. And C++ is regularly criticized for being inconsistent. Do you deepen those criticisms just so that one tiny corner of an API is better?
If I’m honest with myself, I probably would have made the same choices they did in this situation.
Some of the more modern proposals (std::optional is quite old) actually make an explicit appeal to WG21 not to choose consistency at the price of safety because it just needlessly makes the language worse. "But we made the language worse before" is more like a plea for help than an excuse.
Barry Revzin did this in his "do expressions" which are an attempt to kludge compound expressions into C++ which really wants them to be compound statements instead. For consistency, all the obvious mistakes you'll make in do expressions could introduce UB like they would in equivalent C++ core features, but Barry argues they should be Ill-Formed instead - resulting in your mistakes not compiling rather than having undefined behaviour.