Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

cargo init's boilerplate doesn't work with ? #14007

Open
jsha opened this issue Oct 8, 2021 · 10 comments
Open

cargo init's boilerplate doesn't work with ? #14007

jsha opened this issue Oct 8, 2021 · 10 comments
Labels
C-enhancement Category: enhancement Command-init Command-new S-needs-team-input Status: Needs input from team on whether/how to proceed.

Comments

@jsha
Copy link
Contributor

jsha commented Oct 8, 2021

cargo init currently produces boilerplate that looks like this:

fn main() {
    println!("Hello, world!");
}

However, if someone starts from this boilerplate and pastes in examples from documentation that use ?, they'll get an error. main doesn't return Result, so ? can't be used. The API guidelines recommend that examples use ? rather than unwrap.

As an example, if someone tries to paste in this small File example:

use std::fs::File;
use std::io::prelude::*;

fn main() {
    let mut file = File::create("foo.txt")?;
    file.write_all(b"Hello, world!")?;
}

They get some rather confusing errors (what's FromResidual?):

error[E0277]: the `?` operator can only be used in a function that returns `Result` or `Option` (or another type that implements `FromResidual`)
   --> src/main.rs:5:43
    |
4   | / fn main() {
5   | |     let mut file = File::create("foo.txt")?;
    | |                                           ^ cannot use the `?` operator in a function that returns `()`
6   | |     file.write_all(b"Hello, world!")?;
7   | | }
    | |_- this function should return `Result` or `Option` to accept `?`
    |
    = help: the trait `FromResidual<Result<Infallible, std::io::Error>>` is not implemented for `()`
note: required by `from_residual`
   --> /home/jsha/.rustup/toolchains/nightly-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/library/core/src/ops/try_trait.rs:339:5
    |
339 |     fn from_residual(residual: R) -> Self;
    |     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

error[E0277]: the `?` operator can only be used in a function that returns `Result` or `Option` (or another type that implements `FromResidual`)
   --> src/main.rs:6:37
    |
4   | / fn main() {
5   | |     let mut file = File::create("foo.txt")?;
6   | |     file.write_all(b"Hello, world!")?;
    | |                                     ^ cannot use the `?` operator in a function that returns `()`
7   | | }
    | |_- this function should return `Result` or `Option` to accept `?`
    |
    = help: the trait `FromResidual<Result<Infallible, std::io::Error>>` is not implemented for `()`
note: required by `from_residual`
   --> /home/jsha/.rustup/toolchains/nightly-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/library/core/src/ops/try_trait.rs:339:5
    |
339 |     fn from_residual(residual: R) -> Self;
    |     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

For more information about this error, try `rustc --explain E0277`.

This is a problem I remember running into when I was first learning the language. I suspect it may be an issue for other language learners too.

I propose changing the boilerplate to:

use std::error::Error;

fn main() -> Result<(), Box<dyn Error>> {
    println!("Hello, world!");
    Ok(())
}

This compiles cleanly and passes clippy.

@jsha jsha added the T-cargo Team: Cargo label Oct 8, 2021
@jfrimmel
Copy link

jfrimmel commented Oct 8, 2021

I have mixed feelings above this. Personally I use plain fn main() more often than fn main() -> Result<_, _>. Therefore the current cargo template suits my style more than the proposed code. But I'm fine both ways.

But: I think we should improve the current diagnostics to output something like that:

error[E0277]: the `?` operator can only be used in a function that returns `Result` or `Option` (or another type that implements `FromResidual`)
   --> src/main.rs:6:37
    |
4   | / fn main() {
5   | |     let mut file = File::create("foo.txt")?;
6   | |     file.write_all(b"Hello, world!")?;
    | |                                     ^ cannot use the `?` operator in a function that returns `()`
7   | | }
    | |_- help: add a fallible return type: `fn main() -> Result<(), Box<dyn std::error::Error>`

@camelid
Copy link
Member

camelid commented Oct 18, 2021

I also rarely use fn main() -> Result<_, _>. Personally, I think it'd be preferable to just improve the error rather than changing the output of cargo new. I appreciate that the current output of cargo new is quite simple and, at least for binaries, doesn't add extra boilerplate that I don't need.

@jsha
Copy link
Contributor Author

jsha commented Oct 18, 2021

My experience as a beginning Rust dev was that I often needed to cut-and-paste examples from documents, and I was often frustrated by them not working until I remembered to add a -> Result to my main. I also found that the error messages at the time led me down a path of writing Results with a concrete error type rather than a dyn Error. Looking through my juvenilia I see:

fn main() -> io::Result<()> {
fn main() -> Result<(), Box<dyn error::Error>> {
fn main() -> Result<(), Box<dyn std::error::Error>> {
fn main() -> Result<(), Box<dyn std::error::Error + Send + Sync + 'static>> {
fn main() -> Result<(), String> {

I think it's really useful for new users if cargo new offers a simple solution that "just works" in almost all cases. Or to put it another way: as experienced devs, we know we can remove the-> Result boilerplate if we don't want it; or we can just write our own boilerplate, since it's so short. The time spent by experienced devs deleting an unwanted -> Result is outweighed by the frustration experienced by new devs when their copy-pasted code doesn't compile. Learning Rust can be a frustrating experience, and if there's a common error that almost all beginners are likely to hit, I think it's really valuable to remove that error from their experience entirely.

@camelid
Copy link
Member

camelid commented Oct 18, 2021

I didn't hit this as a beginner Rust programmer. I would manually handle my errors and then call process::exit if there was an error. I also don't think I copied and pasted code that would have caused this error.

So maybe many other people did hit this when they were beginners, but I myself don't have the perspective of hitting this as a beginner.

@camelid
Copy link
Member

camelid commented Oct 18, 2021

Also, Box<dyn Error> can add its own confusion in my experience since I sometimes have to cast manually with Box::from or similar to fix inference errors, which even as an experienced user I find confusing.

@weihanglo
Copy link
Member

This should be moved to rust-lang/cargo, no?

Anyway, Cargo has this semi-related issue about cargo-template #5151.

@Enselic
Copy link
Member

Enselic commented Jun 4, 2024

Triage: Yep, this should be moved to cargo. Can someone with sufficient privileges do that please?

@weihanglo weihanglo transferred this issue from rust-lang/rust Jun 4, 2024
@weihanglo weihanglo added Command-new Command-init C-enhancement Category: enhancement S-needs-design Status: Needs someone to work further on the design for the feature or fix. NOT YET accepted. S-needs-team-input Status: Needs input from team on whether/how to proceed. and removed T-cargo Team: Cargo S-needs-design Status: Needs someone to work further on the design for the feature or fix. NOT YET accepted. labels Jun 4, 2024
@epage
Copy link
Contributor

epage commented Jun 4, 2024

@estebank Independent of what we do for cargo init, I feel like this is something a new user could quickly run into and could be made a bit more user friendly. If nothing else, I could see detecting main() and suggesting to change the return type to be Box<dyn Error>.

Thoughts?

@estebank
Copy link
Contributor

estebank commented Jun 4, 2024

@epage yes, I agree we should do that. I am unsure if there's a ticket for it already. We should create one in the compiler's repo for improving this output. We already detect fn main() for to not suggest changing the return type in E0308.

@epage
Copy link
Contributor

epage commented Jun 4, 2024

I went through about 3 pages of diagnostic issues with the error code in them and didn't see something related, so I created rust-lang/rust#125997

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
C-enhancement Category: enhancement Command-init Command-new S-needs-team-input Status: Needs input from team on whether/how to proceed.
Projects
None yet
Development

No branches or pull requests

7 participants