Closed aturon closed 1 year ago
This is what I think:
Returning Result<T, E>
should use the debug impl for E. This is the most convenient trait -- it's widely implemented, and satisfies the "quick and dirty output" use case as well as the unit testing use case. I'd prefer not to use Display
both because it is less widely implemented and because it gives the impression that this is polished output, which I think is highly unlikely.
But there should also be a way to get professional looking output. Fortunately, there are already two such ways. First, as @withoutboats said, people can make a "display-from-debug" bridge if they want to use E: Display
or otherwise give professional style output. But if this feels too hacky, then you can also just define your own type to replace result. e.g., you might do:
fn main() -> ProfessionalLookingResult {
...
}
and then implement Try
for ProfessionalLookingResult
. Then you can implement Terminate
as well, whatever:
impl Terminate for ProfessionalLookingResult {
fn report(self) -> i32 {
...
eprintln!("Something very professional here.");
return 1;
...
}
}
I agree with @nikomatsakis that this should use Debug
.
I also think that for polished output, writing some code in main
is probably better than creating a new type to implement Try
and Terminate
. The point of Terminate
seems to me to be for things libraries can easily make a good default for, which is just never the case for a sitution in which how the program terminates is significant to end users (e.g. professional CLIs).
Of course other people may have a different opinion, and there are multiple ways to use the traits involved to inject code instead of writing it directly in main
. What's great is that we have multiple choices and we don't have to come up with a single blessed way to handle errors always.
Let me just jot down some thoughts, though there are a few problems with them.
I'd love to see the following
fn main() -> i32 {
1
}
Which could be written more generally as:
fn main() -> impl Display {
1
}
Both of these main functions should return a 0 exit code and println!
the Display
of 1.
This should be as simple as the following (I'd think).
impl<T> Termination for T where T: Display {
fn report(self) -> i32 {
println!("{}", self);
EXIT_SUCCESS
}
}
Then for errors we can have:
impl<T: Termination, E: Debug> Termination for Result<T, E> { ... }
where the implementation is the same as in the RFC just with "{:?}"
to use
a Debug
format.
As mentioned before, people who need more control over the output can simply write:
fn main() -> Result<i32, MyError> { ... }
impl Termination for Result<i32, MyError> { ... }
Though this would be undecidable with our current compiler I guess, since it would see conflicting implementations... So we do what @nikomatsakis suggests and write:
fn main() -> MyResult { ... }
impl Termination for MyResult { ... }
or, if you want something more general.
impl<T, E> Termination for MyResult<T, E> { ... }
I know this is partly restating things that have been said, but I thought I'd present my vision altogether, showing that a more general solution for displaying return values, not just results. Seems like a lot of this comment is arguing what implementations of Termination
we ship by default. Also this comment is at odds with implementation like impl Termination for bool
as described in the RFC. I personally think that non-zero exit codes should be handled exclusively by Results
or custom types that implement Termination
.
It's an interesting question of how to then handle ?
on Option
types in main since they don't have an implementation for Display
.
TL;DR: I am OK with Debug
.
More detailed explanation: I spent some time thinking about this yesterday and today with an aim to uncover implicit assumptions I have or am making to help arrive at the best possible answer.
Key Assumption: Of all the various types of applications that one can write in Rust, I think the console app stands to benefit the most/be most impacted by this decision. My thinking is that when writing a library, a AAA game title, an IDE or a proprietary control system, one would probably not be expecting a default main termination trait to meet one's need out of the box (indeed, one might not even have a main). So my bias toward Display
as default comes from what we expect to see when using a small command-line application--for example:
$ cd foo
bash: cd: foo: No such file or directory
Most of us do not expect any sort of debugging aids, just a succinct indicator of what went wrong. I am simply advocating for this as a default position.
When I think of writing a Terminate
impl to get simple output like this, I realize that the ?
from main feature isn't that different from stable rust today (in terms of amount of code written), where a Result
-aware "inner_main()" is often created in order to handle E.
With this assumption in mind, as a thought exercise, I tried to determine whether I felt strongly that the preponderance of "inner_main()
"-style implementations in existence today were of a more-casual Display
flavor (over a more-technical Debug
flavor). My thinking was that this would be an indication of how the feature is likely to actually be used.
I was unable to convince myself that this is be the case. (Meaning, I don't think there currently exists a strong bias toward Display
in existing implementations). Indeed, when looking through my own repositories that I've written over the past 16 months, I also found enough of both cases that I can't say that implementing Display
by default would have amounted to a net savings.
Holding to the "primary beneficiary is the cli application" assumption, there are a great number of console apps which provide help and usage information. For example:
$ git foo
git: 'foo' is not a git command. See 'git --help'.
The most similar command is
log
So even in for console apps, it's hard for me to identify an "injured group" by going with Debug
.
And finally, I'd be happier with a Debug
impl than with the feature held up for another 6 months, too, so, selfishly, there's that :).
So there's my thought process laid out in public. To summarize, I think Debug
will no better and no worse than Display
, and as such, should be fine as the default implementation.
Like many of you, I'm sure, I do wish there was an implementation I felt more excited about--like, "YES, THAT'S IT!!!", TBH. But maybe that's just my expectations being unrealistic... Maybe once we have a solution that works with failure
cutting down boilerplate in my projects, it'll grow on me. :)
Note I opened a PR to support the terminatio trait in tests: #48143 (building on @bkchr's work).
I took the liberty of extending the Termination
trait with a method for processing the result of the test. This simplified the implementation, but it also makes sense, since test failures may want more verbose output than executable failures.
Termination
should be renamed Terminate
after our general preference for verbs for traits in libstd.
@withoutboats I think at some point there was a discussion that verb traits are mostly those that have a single method with the same name as the trait. Anyhow, might I float again my own bikeshed suggestion, Exit
?
Gratuitous bikeshedding: this is a single-method trait. If we want to give them the same name, maybe ToExitCode
/ to_exit_code
?
Stabilizing returning Result
can be done independently from stabilizing the trait, right?
To me, this feels much like ?
where most of the value comes from the language feature, and we can delay on figuring out the trait. The RFC discussion even made me wonder if the trait ever needs to be stabilized, since putting the code in an inner_main
seemed easier than a trait impl for this...
Yes, we don't need to stabilize the trait - though it is useful on stable for things like frameworks, which can't necessarily rely so much on inner_main
.
@SimonSapin I think of To
as refering to a type conversion, and this doesn't. But we could name the method terminate
(also I don't think this restriction on when to name traits verbs holds up. Try
is an obvious counter example.)
I have proposed that we stabilize fn main() -> T
where T
is not unit. This leaves many details -- particularly the name/location/details of the trait -- unstabilized, but some things are fixed. Details here:
https://github.com/rust-lang/rust/issues/48453
Please give your feedback!
terminate
seems to be more descriptive than report
. We always terminate
, but may omit report
ing.
But unlike for example std::process::exit
this method does not terminate anything. It only converts the return value of main()
into an exit code (after optionally printing Result::Err
to stderr).
Another vote for Exit
. I like that it is brief, quite descriptive and consistent with traditional concept of exit codes/exit status.
I'd definitely prefer Exit
over Terminate
since we're gracefully exiting by returning from main, rather than suddenly hard terminating where we are because something went really wrong.
I added https://github.com/rust-lang/rust/issues/48854 to propose stabilizing unit tests that return results.
Oh hey, i found the proper place to talk about this.
?
in doctestsThe way doctests currently work is something like this:
fn main
//
)fn main
was found, it won't touch what's already therefn main
was not found, it will wrap most* of the doctest in a basic fn main() { }
#![inner_attributes]
and extern crate
declarations and put them outside the generated main function, but everything else goes inside.std
) then rustdoc will also insert an extern crate my_crate;
statement right before the generated main function.(I left a couple details out, but i conveniently made a full writeup here.)
So, to use ?
seamlessly in a doctest, the bit that needs to change is the part where it adds fn main() { your_code_here(); }
Declaring your own fn main() -> Result<(), Error>
will work as soon as you can do that in regular code - rustdoc doesn't even need to be modified there. However, to make it work without declaring main manually will require a small tweak. Having not closely followed this feature, i'm not sure if there's a one-size-fits-all solution. Is fn main() -> impl Termination
possible?
Is fn main() -> impl Termination possible?
In a shallow sense, yes: https://play.rust-lang.org/?gist=8e353379f77a546d152c9113414a88f7&version=nightly
Unfortunately, I think that -> impl Trait
is fundamentally troublesome with ?
due to the inbuilt error-conversion, which needs the inference context to tell it what type to use: https://play.rust-lang.org/?gist=23410fa4fa684710bc75e16f0714ec4b&version=nightly
Personally I was imagining ?
-in-doctests working via something like https://github.com/rust-lang/rfcs/pull/2107 as fn main() -> Result<(), Box<Debug>> catch { your_code_here(); }
(using syntax from https://github.com/rust-lang/rust/issues/41414#issuecomment-373985777).
The impl Trait
version is cool, though, and might work if there was some sort of "prefer the same type if uncontrained" support that rustc could use internally in the ?
desugar, but my recollection is that the idea of a feature like that tends to make the people who understand how things work recoil in horror :sweat_smile: But maybe it being an internal thing that only applies if the output is impl Trait
would be feasible...
Ooh, those are very real concerns. I'd forgotten about how that would mess up type inference. If the catch block started Ok-wrapping like that linked issue, then that seems like a much easier (and much quicker, stabilization-wise) path forward.
The only thing i wonder is how that will be affected by the edition transition. Isn't the catch
syntax changing in the 2018 epoch? Rustdoc will likely want to compile doctests in the same edition as the library it's running, so it would need to differentiate between the syntaxes based on the epoch flag it's been handed.
I'm concerned that this is now stabilized but simple cases apparently still ICE: https://github.com/rust-lang/rust/issues/48890#issuecomment-375952342
fn main() -> Result<(), &'static str> {
Err("An error message for you")
}
assertion failed: !substs.has_erasable_regions(), librustc_trans_utils/symbol_names.rs:169:9
https://play.rust-lang.org/?gist=fe6ae28c67e7d3195a3731839d4aac84&version=nightly
At what point do we say "this is too buggy and we should unstabilize"? Seems like if this hit the stable channel in its current form, it would cause a lot of confusion.
@frewsxcv I think the problems are fixed now, right?
@nikomatsakis the issue I raised in https://github.com/rust-lang/rust/issues/48389 is resolved in 1.26-beta, so yes from my perspective.
Yep, the ICE I was worried about is fixed now!
Apologies for chiming in at a point in which it's probably too late to do anything about this, but I wanted to leave my feedback here in case there was. I did read most of this thread, so I speak with that context in mind. However, this thread is long, so if it looks like I've overlooked something, then I probably did and I would appreciate having it pointed out to me. :-)
TL;DR - I think showing the Debug
message of an error was a mistake, and that a better choice would be to use the Display
message of an error.
At the core of my belief is that, as someone who routinely builds CLI programs in Rust, I can't ever remember caring much about what the Debug
message of an Error
is. Namely, the Debug
of an error is, by design, for developers, not for end users. When you build a CLI program, its interface is fundamentally intended to be read by end users, so a Debug
message has very little utility here. That is, if any CLI program I shipped to end users showed the debug representation of a Rust value in normal operation, then I would consider that a bug to be fixed. I generally think this should be true of every CLI program written in Rust, although I understand it may be a point on which reasonable people can disagree. With that said, a somewhat startling implication of my opinion is that we've effectively stabilized a feature where its default mode of operation starts you off with a bug (again, IMO) that should be fixed.
By showing the Debug
representation of an error by default, I also think we are encouraging poor practice. In particular, it is very common in the course of writing a Rust CLI program to observe that even the Display
impl of an error is not good enough to be consumed by end users, and that real work must be done to fix it. A concrete example of this is io::Error
. Showing an io::Error
without a corresponding file path (assuming it came from reading/writing/opening/creating a file) is basically a bug, because it is difficult for an end user to do anything with it. By electing to show the Debug
representation of an error by default, we've made it harder for those sorts of bugs to be uncovered by folks creating CLI programs. (On top of that, the Debug
of an io::Error
is much less useful than its Display
, but that on its own isn't a huge pain point in my experience.)
Finally, to round out my argument, I also have a hard time envisioning the circumstances under which I would use ?-in-main
even in examples. Namely, I've been trying to write examples that match real world programs as closely as possible, and this generally has involved writing things like this:
use std::error::Error;
use std::process;
fn try_main() -> Result<(), Box<Error>> {
// do stuff with `?`
}
fn main() {
if let Err(err) = try_main() {
eprintln!("{}", err);
process::exit(1);
}
}
On its face, it would be lovely to replace this with ?-in-main
, but I can't, because it won't show the Display
of an error. That is, when writing a real CLI program, I will in fact use the above approach, so if I want my examples to reflect reality, then I think I should show what I do in real programs and not take shortcuts (to a reasonable extent). I actually think this sort of thing is really important, and one side effect of this historically was that it showed folks how to write idiomatic Rust code without sprinkling unwrap
everywhere. But if I revert to using ?-in-main
in my examples, then I've just reneged on my goal: I'm now setting up folks who may not know any better to just write programs that, by default, emit very unhelpful error messages.
The pattern of "emit an error message and exit with an appropriate error code" is actually used in polished programs. For example, if ?-in-main
used Display
, then I could merge the main
and run
functions in ripgrep today:
Of course, I could use ?-in-main
in the future by providing my own impl for the Termination
trait once that stabilizes, but why would I bother doing that if I could just write out the main
function I have? And even so, this doesn't help with my conundrum when writing examples. I'd need to include that impl
in the examples in order to make them match reality, and at that point, I might as well stick with the examples I have today (using a main
and a try_main
).
From the looks of it, it appears that fixing this would be a breaking change. That is, this code compiles on Rust stable today:
#[derive(Debug)]
struct OnlyDebug;
fn main() -> Result<(), OnlyDebug> {
Err(OnlyDebug)
}
I think switching to Display
would break this code. But I don't know for sure! If this is truly a ship-has-sailed issue, then I understand and there's not too much use in belaboring the point, but I do feel strongly enough about this to at least say something and see if I can't convince others and see if there's anything that can be done to fix it. (It's also quite possible that I am overreacting here, but I've so far had a couple people ask me "why aren't you using ?-in-main
?" in my CSV examples, and my answer has basically been, "I don't see how it would ever be feasible to do that." Maybe that wasn't a problem that was intended to be solved by ?-in-main
, but some folks certainly had that impression. With its current implementation, I could see it being useful in doc tests and unit tests, but I have a hard time thinking of other situations in which I would use it.)
I agree that displaying the Display
over the Debug
is a better for CLI applications. I don't agree that that it should be Display
instead of Debug
, because that drastically limits what errors can actually be ?
-ed and defeats the purpose of ?-in-main
. As far I can tell(Could totally be wrong as I don't have the time to compile stdlib and I don't know if specialisation covers this) there's no reason we can't add the following impl to Termination. This would provide a non-breaking way to use Display
when that is available and fall back to Debug
when it doesn't.
#[unstable(feature = "termination_trait_lib", issue = "43301")]
impl<E: fmt::Display> Termination for Result<!, E> {
fn report(self) -> i32 {
let Err(err) = self;
eprintln!("Error: {}", err);
ExitCode::FAILURE.report()
}
}
Finally, to round out my argument, I also have a hard time envisioning the circumstances under which I would use ?-in-main even in examples.
I have to agree with @BurntSushi in general for real CLI programs, but for random scripts and internal tools that only I plan to use, this is really convenient. Also, it's really convenient for prototypes and toys. We could always discourage actually using it in production code, right?
Part of the goal with ?-in-main is to avoid unidiomatic code in code examples, namely unwraps. But if ?-in-main itself becomes unidiomatic, then that undermines the entire feature. I would strongly like to avoid any outcome that would cause us to have to discourage using it, in production code or otherwise.
I've been trying to use this feature in main and I've found it pretty frustrating. The existing impls of the termination trait just don't allow me to conveniently "accumulate" multiple kinds of errors -- for example, I can't use failure::Fail, because it doesn't implement Error; I can't use Box
, same reason. I think we should prioritize changing to Debug. =)
If we use Display
, we can introduce a fast and dirty type like MainError
to accumulate multiple kinds of errors:
pub struct MainError {
s: String,
}
impl std::fmt::Display for MainError {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
self.s.fmt(f)
}
}
impl<T> From<T> for MainError where T: std::error::Error {
fn from(t: T) -> Self {
MainError {
s: t.to_string(),
}
}
}
This will allow something like below similar to Box<Error>
:
fn main() -> Result<(), MainError> {
let _ = std::fs::File::open("foo")?;
Ok(())
}
Previous discussion of Display vs Debug is in the hidden part here, starting around https://github.com/rust-lang/rust/issues/43301#issuecomment-362020946.
@BurntSushi I agree with you. If this can't be fixed, there could be a workaround:
use std::fmt;
struct DisplayAsDebug<T: fmt::Display>(pub T);
impl<T: fmt::Display> Debug for DisplayAsDebug {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
fmt::Display::fmt(&self.0, f)
}
}
impl<T: fmt::Display> From<T> for DisplayAsDebug {
fn from(val: T) -> Self {
DisplayAsDebug(val)
}
}
If this was in std::fmt
, we could use something like this:
use std::{fmt, io};
fn main() -> Result<(), fmt::DisplayAsDebug<io::Error>> {
let mut file = File::open("/some/file")?;
// do something with file
}
I still don't have time to contribute any more to this for the foreseeable future, but I also agree that ?
in main ought to be usable in serious CLI programs, and there were features in the original draft of the RFC that were intended to facilitate that. They got dropped in the name of incrementalism but maybe they should be revisited.
I think it's fine to fix this in a backwards incompatible way if it's done soon, before much code out there is using it on stable.
As somebody who writes a lot of CLI programs in Rust, and who's in charge of Rust style at work, I pretty much agree with @burntsushi here. I would have happily used the version of this feature specified in the RFC.
But I consider showing a Debug
implementation to a user to be a bug, and not much better than just calling unwrap
everywhere in a CLI program. So even in example code, I can't imagine ever using this version of the feature. This is too bad, because removing boilerplate from main
would have simplified the learning for new Rust developers at work, and we wouldn't have needed to explain quick_main!
or the equivalent any more.
Pretty much the only circumstances where I can imagine using this would be to eliminate unwrap
from doctests. But I don't know if that's supported yet.
If we use
Display
, we can introduce a fast and dirty type likeMainError
to accumulate multiple kinds of errors:
Personally, this sort of workaround would add sufficient complexity that it's just easier to avoid ?
-in-main
completely.
@Aaronepower:
As far I can tell(Could totally be wrong as I don't have the time to compile stdlib and I don't know if specialisation covers this) there's no reason we can't add the following impl to Termination. This would provide a non-breaking way to use Display when that is available and fall back to Debug when it doesn't.
If this would enable me to write fn main() -> Result<(), failure::Error>
and get a nice, human-readable error using Display
, it would definitely satisfy my major concerns. However, I suppose that this would still leave the question of optionally displaying the backtrace in failure::Error
when RUST_BACKTRACE=1
is set—but that might be out of scope, anyways, or at least an issue which should be taken up with failure
.
This was stabilized some time ago, and I don't think we can really change the behavior of Result
. I personally remain pretty happy with the current behavior for the use cases that I tend to do — quick and dirty scripts and the like — but I agree that more complex scripts won't want it. However, at the time of the discussion, we did also discuss a number of ways that you could control that behavior (I can't seem to find those comments now because github is hiding things from me).
For example, you could define your own "wrapper" for the error type, and implement From
for it:
struct PrettyPrintedError { ... }
impl<E: Display> From<E> for PrettyPrintedError { }
impl Debug { /* .. invoke Display .. */ }
Now you can write something like this, which means you can use ?
in main
:
fn main() -> Result<(), PrettyPrintedError> { ... }
Perhaps such a type should be part of quick-cli or something?
@nikomatsakis Yeah, I totally get that workaround, but I do feel like that defeats the purpose of the succinctness of using ?-in-main
. I feel like "it's possible to use ?-in-main
using this work-around" undermines ?-in-main
itself unfortunately. For instance, I'm not going to write out your work-around in succinct examples and I'm not going to impose a dependency on quicli
for every example I write either. I'm certainly not going to use it for quick programs either, because I want the Display
output of an error in basically every CLI program I write. My opinion is that the debug output of an error is a bug in CLI programs that you put in front of users.
The alternative to ?-in-main
is an additional ~4-line function. So if the workaround to fixing ?-in-main
to use Display
is much more than that, then I personally don't see much reason to use it at all (outside of doc tests or unit tests, as mentioned above).
Is this something that could be changed in the edition?
@BurntSushi How would you feel about the workaround being in std
, so you'd only have to write a bit longer return type declaration on main()
?
@Kixunil My initial feeling is that it might be palatable. Haven't given it too much thought though.
@BurntSushi
The alternative to
?-in-main
is an additional ~4-line function.
Ultimately, the behavior is stable, and I don't think we can realistically change it at this point. (I mean it's not a soundness violation or something.)
That said, I still find the current setup pretty nice and preferable to Display
, but I guess it's a matter of what you want the "default" to be — that is, either setup can be made to work like the other via various newtypes. So either we default to favoring "quick-n-dirty" scripts, which want Debug
, or polished end products. I tend to think a polished end product is where I can afford an additional import readily enough, and also where I'd like to pick from many different possible formats (e.g., do I want just the error, or do I want to include argv[0], etc).
To be more concrete, I imagine it would look like this:
use failure::format::JustError;
fn main() -> Result<(), JustError> { .. }
or, to get a different format, perhaps I do this:
use failure::format::ProgramNameAndError;
fn main() -> Result<(), ProgramNameAndError> { .. }
Both of these feel reasonably nice compared to the 4-line function you had to write before:
use std::sys;
fn main() {
match inner_main() {
Ok(()) => { }
Err(error) => {
println!("{}", error);
sys::exit(1);
}
}
fn inner_main() -> Result<(), Error> {
...
}
I'm coming around on this topic since, for production quality, adding what amouts to essensially a custom error type for the output seems like a good direction to push people anyway. This seems to paralell how panic!
, for example, gives debug quality messages by default.
@nikomatsakis That is perhaps a better long term view, and I do find your end result appetizing. It would be nice if those error types ended up in std
some day so that they can be used in examples with as little overhead as possible. :-)
Process note: I tried to frame my initial feedback here with "is there anything that can be done" rather than "let's change something that has already stabilized," in an effort to focus on the high level goal rather than ask for something that we certainly cannot do. :-)
Well that didn't take long: https://crates.io/crates/exitfailure 😆
I am wondering how many people are surprised by using Debug
trait versus Display
trait. Both the initial discussion draft and the final RFC is using Display
. This is similar to the universal impl trait incident, where people thought they are getting one thing, but only know they are getting another after it is getting stabalized. Also considering a lot of people who are surprised won't be using this feature, I feel there won't be big backslash if we change it in the next edition.
@WiSaGaN The details of an RFC can and will change from time to time. This is expected and healthy. RFCs are design documents and not a perfect representation of what is and always will be. Information discovery is hard, and it's my fault for not paying more attention. I'm hoping that we can avoid relitigating the stability of this feature and instead focus on the higher level goals that we can feasibly act upon.
@nikomatsakis The main issue I see with favoring quick-n-dirty cases is that there already is a way to address them: .unwrap()
. So from that perspective, ?
is just another way to write quick-n-dirty things, yet there's no similarly easy way to write polished things.
Of course the ship has sailed, so we're mostly screwed. I'd love to see this changed in next edition, if possible.
@Kixunil, unwrap
is really not a good way to address things, as the title of this thread states we would like to be able to use the syntactic sugar ?
in main
. I can understand where you are coming from (in fact it was part of my initial hesitation with the use of ?
for option handling) but with the inclusion of ?
in the language this issue is a pretty nice usability win IMHO. If you need nicer output, then there are options for you. You don't release things to users without testing them first, and the discovery that you need a custom type for the output of main
will be a pretty quick realization.
As for "why" we'd like ?
in main
, well consider how weird it is now. You can use it practically everywhere else (since you have control of the return type). The main
function then ends up feeling kinda special, which it shouldn't.
.unwrap()
is a quick-and-dirty solution that doesn't compose, so you can't factor code in and out of main()
while you're sketching out a program.
By comparison, ?
is a quick-and-dirty solution that does compose, and making it not-quick-and-dirty is a matter of putting the correct return-type on main()
, not of modifying the code itself.
@Screwtapello ah, now it makes sense to me. Thanks!
I just want to express that I thought the aim of this was to bring ?
in main
for all applications without having to resort to additional wrappers... If it's just for testing I don't see much benefit in it and will keep sticking to .unwrap()
, sadly.
This is a tracking issue for the RFC "
?
inmain
" (rust-lang/rfcs#1937).Steps:
?
inmain
(#46479)?
in doctest?
in#[test]
Stabilizations:
main
with non-() return types (https://github.com/rust-lang/rust/issues/48453) Merged in https://github.com/rust-lang/rust/pull/49162Related issues:
Unresolved questions:
this will be stabilized by #48453no longer true after https://github.com/rust-lang/rust/pull/48497