Git Product home page Git Product logo

fehler's People

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

fehler's Issues

Consider using self::Error if present, otherwise crate::Error

It would be convenient if a simple use path::to::Error; were able to override the choice of default error type for a module. Crates sometimes want to use something like anyhow::Error throughout, but can have a module or two where a more specialized type makes more sense. Specifying in an attribute throws(std::io::Error) to every function in a module is annoying.

pub struct Error;

pub mod m {
    #[throws(_)]
    pub fn f() {
        throw!(crate::Error);
    }
}

pub mod n {
    use some::other::Error;

    #[throws(_)]
    pub fn f() {
        throw!(some::other::Error);
    }
}

Parsing problem(?) with Box<dyn FnOnce()>

Another oddity I'm afraid. This:

use fehler::throws;

#[throws(std::io::Error)] // ERROR: Wrapper type must be a normal path type
pub fn zonk() {
  let mut _unused : Box<dyn FnOnce()> = Box::new(||());
}

pub fn works0() -> Result<(), std::io::Error> {
  let mut _unused : Box<dyn FnOnce()> = Box::new(||());
  Ok(())
}

pub fn works1() {
  let mut _unused : Box<dyn FnOnce()> = Box::new(||());
}

#[throws(std::io::Error)]
pub fn works2() {
  let mut _unused : Box<()> = Box::new(());
}

Produces this:

error: custom attribute panicked
 --> src/lib.rs:4:1
  |
3 | #[throws(std::io::Error)] // ERROR: Wrapper type must be a normal path type
  | ^^^^^^^^^^^^^^^^^^^^^^^^^
  |
  = help: message: Wrapper type must be a normal path type

I think zonk should compile and be equivalent to works0. Again, this test case is minimised from a larger program. Thanks for your attention.

Consider renaming `error!` to avoid collision with `log::error!`

As a person who uses Rust in production, it was confusing at first to see an error! macro defined by fehler. I understand that this can easily be handled in Rust 2018 by simply renaming imports:

use log::error as log_error;
// ...or, alternatively:
use fehler::error as throw_err;

...but it'd be nice to remove this papercut altogether and not force people to pick a solution. I'm not partial to what the symbol name ends up being -- just wanted to point out the issue!

How do exceptions propagate?

If I throw an exception in a function what happens in the calling function? If it's not a throwing function it will receive a Result but if it is, what happens? Receiving a Result in that case may make exceptions much less powerful.

Warnings of unreachable code

Codes like

#[throws(NotFoundError)]
fn search() {
    for item in items {
       if item.is_good() { items_found.add(item); return; }
    }
    throw!("Item")
}

will cause a warning like

warning: unreachable expression
  --> src\item_lookup.rs:45:1
   |
45 | #[throws(NotFoundError)]
   | ^^^^^^^^^^^^^^^^^^^^^
   | |
   | unreachable expression
   | any code following this expression is unreachable
   |
   = note: `#[warn(unreachable_code)]` on by default
   = note: this warning originates in an attribute macro (in Nightly builds, run with -Z macro-backtrace for more info)

This is because there is an extra Ok return after the throw. This can only happen when the return type is ().

This code pattern would be very common.

My solution:

#[throws(NotFoundError)]
fn search() {
    for item in items {
       if item.is_good() { items_found.add(item); return; }
    }
    // Does not work:
    // #[allow(unreachable_code)] throw!("Item") --> still warning, attribute get removed in expansion
    // ---
   //  return throw!("Item"); --> still warning
    #[allow(unreachable_code)]
    return throw!("Item");
}

Make a new release?

Hi!

Many projects turn warnings into errors, which can make using the latest fehler release (necessary if you want to publish on crates.io) a bit unergonomic, as seen in #53, #52 and #42. From what I read in the issues, is the concern that this would be a breaking change? It would be nice to get a new release which includes these fixes.

Thanks!

#[throws] on methods ending with "loop" warns about "unreachable expression"

#[fehler::throws]
fn main() {
    potentially_failing_operation()?;
    loop {
        potentially_failing_operation()?;
    }
}

Results in a warning

  --> src/main.rs:5:1
   |
5  |   #[fehler::throws]
   |   ^^^^^^^^^^^^^^^^^ unreachable expression
...
8  | /     loop {
9  | |         potentially_failing_operation()?;
10 | |     }
   | |_____- any code following this expression is unreachable

Expected to be no warning.

I'll try to dig into the source to see if this is possible/trivial to fix.

Script for easy reproduction

cargo new fehler-unreachable
cd fehler-unreachable
echo 'fehler = "*"' >> Cargo.toml

echo '
#[derive(Debug)]
struct Error;

#[fehler::throws]
fn main() {
    loop {
    }
}
' > src/main.rs

cargo check

P.S.
Thanks for the awesome crate, really cleans up the code!

must_use on throws fn

Hi. Thanks for this nice crate. I have only been using it a few hours and it is already improving my life. I have found a situation where I can write something with an obvious meaning but which doesn't do what I would have hoped:

This function

#[must_use] #[throws(X)] fn x() ->  usize { 42 }

can be called, and the value 42 discarded, without any warning. This is probably not what the programmer intended when they wrote #[must_use].

I suspect that fixing this is not going to be simple. When considering Rust without Fehler, there does not appear to be a way to write a function that returns a Result<T,E> where the caller is required to both handle E and do something with T. (Other than making the whole type T must_use, which may not be desirable or possible.)

My full test program:

use fehler::throws;
use thiserror::Error;

#[derive(Error,Debug)]
#[error("X")]
struct X ();

#[must_use] #[throws(X)] fn x() ->        usize    {    42  }
#[must_use]              fn y() -> Result<usize,X> { Ok(43) }
#[must_use]              fn z() ->        usize    {    44  }

#[throws(X)]
fn main() {
                               x()?;
                               y()?;
  { #![allow(unused_must_use)] z(); }
}

How to let a thousand flours bloom

Der Fehler is intentionally limited to a predefined set of APIs that I picked when I started working on it, and I'm not interested in adding more to it - such as APIs to enable other patterns of constructing errors. It's also very actively "my" library, and I intend to release it as 1.0 soon and after that make only superficial future changes to it.

But a goal of the project is definitely to pull the ecosystem back toward the Error trait, rather than failure::Fail. I want other people to write other crates for their own error handling patterns that they like. I want to write the next iteration of this guidance with more than 4 patterns, linking to several different crates. I want this project to encourage experimentation by other people in their own libraries, to find other patterns.

Along similar lines, it's been pointed out in #12 that throw and throws in particular could make a lot of sense as an independent no_std library. And in #4 we've discussed another pattern that I've always liked (an error and errorkind pair), but that doesn't get used because it involves a lot of boilerplate. Maybe before releasing a 1.0 of this library, I should actually be releasing several libraries at the same time, to encourage a precedent that this isn't some all-encompassing solution.

Clarify the role of context?

In thinking about this topic, I guess there are both primary and secondary contexts for an error.

  • Primary: The problem that occurred that a user may downcast to.
    • e.g. Failed to open file
  • Secondary: Information to help the user resolve the issue
    • Sometimes people might want to programmatically act upon it. One common case I've seen is when it is unclear if the context has already been added, so you check and then add it later.
    • e.g. file path (if the original error didn't include it)

I get mixed signals of which role Context is supposed to fill

  • error field is required, making it seem like it is only for Secondary or is Primary but limited to masking other errors
  • ContextError treats the context as the payload of an error, as if it was Primary

One side effect of this is that the displayed error looks wonky with Context. For example, I previously looked at failure for a library I wrote. The custom errors look like

Error: liquid: Expected whole number, found `fractional number`
  with:
    end=10.5
from: {% for i in (1..max) reversed %}
from: {% if "blue skies" == var %}
  with:
    var=10

The original error "liquid: Expected whole number, found fractional number" is the error someone should programmatically act on. I then add two different kinds of contexts as we unwind the stack, "with"s for data inside a stack frame and "from" for a stack from location.

When considering this for failure, I would have gotten

Error: liquid: Expected whole number, found `fractional number`
cause: end=10.5
cause: {% for i in (1..max) reversed %}
cause: var=10
cause: {% if "blue skies" == var %}

(might have this inverted; can't remember)

Related to this is a point I raised in https://github.com/mgattozzi/terminator/issues/3 of whether we should treat the cause / source as user-visible context or internal debug data. If I use source for context and my original error is a std::io::Error which might not be user-friendly, I need to make choice of displaying it anyways or intentionally dropping it.

non-result throws

Would like to support:

#[throws(as Option)]

etc

The problem is with the throw! macro. If the try trait were stable, we could instead run this:

return <_ as core::ops::Try>::from_err(..)

But that would make this a nightly only crate.

I'm not sure if

Design notes

I'd make the syntax work like this:

#[throws(as $wrap)]
fn function() -> $ret

// translated to:
fn function() -> $wrap<$ret>

Whereas:

#[throws($err as $wrap)]
fn function() -> $ret

// translates to:

fn function() -> $wrap<$ret, $err>

This is why I modified the default error type feature to require an explicit _.

Does Fehler have any abstraction over std::error::Error?

Hello!

Some other error handling crates, like anyhow, have abstractions over std::error::Error (or custom error types in older crates). Does Fehler have something like that, or should I be used std::error::Error myself, or should I be using another crate that wraps around that? By that question, I mean what would seem to cause the least friction in using the library?

Thank you very much and apologies for submitting a question as an issue!

Collaborate on a "core::Error" crate with snafu, anyhow and other error crates

With the recent proliferation of error-handling crates, it has become clear that the situation around the lack of a core::error::Error is really suboptimal. In snafu, no_std support is being introduced through a whole new Error trait just for no_std - which could lead to similar problems that failure had by becoming incompatible with the ecosystem.

Ideally, the Error trait would show up in core, but due to coherence concerns and std-dependent features being added to std::error::Error, a resolution is unlikely to happen soon. As such, I propose making a new crate, core-error - exposing our own version of the Error trait. The goal of this crate is twofolds:

  • Provide a common trait for various error handling crates (Failure, Snafu, Fehler, Anyhow, error_chain, and any other)
  • Allow no_std libraries that don't want to depend on a specific error handling crate to still expose errors that can interoperate with those libraries.

Such a crate would work like this:

  • With the std feature, it just re-export std::error::*
  • With no-default-features, it exposes an Error trait similar to the one in std but without backtraces, and without the std/alloc-only impls.
  • Rustc version auto-detection is used to figure which errors to implement the trait on.
  • The alloc feature enables downcasting, in addition to supporting alloc errors
  • The crate would compile on all versions from 1.0.0 to the latest stable version.

The trait will be compatible with std's Error trait, and if libcore gains an Error trait in the future, it should be compatible with it too.

Once the crate reaches 1.0.0, I'll consider it ready for integration in the various error crates and will follow the same stability guarantee Rust does: No breaking changes ever.

Work has already started in https://github.com/core-error/core-error. I'd be interested in hearing feedback on the design.

Documentation

Would be nice to have actually good documentation here

Missing dead_code warnings

use anyhow::Error;
use fehler::throws;

#[throws]
fn f() {}

fn g() {}

fn main() {}
warning: function is never used: `g`
 --> src/main.rs:7:4
  |
7 | fn g() {}
  |    ^
  |
  = note: `#[warn(dead_code)]` on by default

It seems throws functions aren't seen by the compiler as dead code when they should be. Probably some Span is wrong somewhere.

(Tested as of master @ df2e3d4.)

Name of the crate

Considering renaming this library to exceptional. The problem with fehler is of course how close the pronunciation is to failure. Also, the name fehler was based on this being a "successor" to failure, but the crate that is a successor to failure in my opinion is really anyhow.

Examples

Yes, it would be nice and helpful to have more examples, especially for newbies like me.

Unexpected "unreachable expression" warning in function with a loop

Example code:

use fehler::throws;
use std::io::Error;

fn no_warning() -> Result<(), Error> {
    loop {
        return Ok(());
    }
}

#[throws]
fn unexpected_warning() {
    loop {
        return;
    }
}

fn main() {
    no_warning().unwrap();
    unexpected_warning().unwrap();
}

Gives this warning:

warning: unreachable expression
  --> src/main.rs:10:1
   |
10 |   #[throws]
   |   ^^^^^^^^^ unreachable expression
11 |   fn unexpected_warning() {
12 | /     loop {
13 | |         return;
14 | |     }
   | |_____- any code following this expression is unreachable
   |
   = note: `#[warn(unreachable_code)]` on by default
   = note: this warning originates in an attribute macro (in Nightly builds, run with -Z macro-backtrace for more info)
$ rustc --version
rustc 1.43.0 (4fb7144ed 2020-04-20)

Am i misusing the library ?

I have this method

    #[throws(i32)]
    fn literal(&mut self) -> Literal<'a> {
        match self.peek() {
            Some(Token {
                kind: lit @ (TokenKind::Num(_) | TokenKind::Bool(_) | TokenKind::Unit),
                range,
            }) => Literal {
                pos: range.clone(),
                kind: match lit {
                    TokenKind::Num(n) => LiteralKind::Num(*n),
                    TokenKind::Bool(b) => LiteralKind::Bool(*b),
                    TokenKind::Unit => LiteralKind::Unit,
                    _ => unreachable!(),
                },
            },
            _ => throw!(5),
        }
    }

and i get, when i try to compile my code:

error: custom attribute panicked
  --> src\parser\parser.rs:18:5
   |
18 |     #[throws(i32)]
   |     ^^^^^^^^^^^^^^
   |
   = help: message: #[throws] attribute can only be applied to functions and methods

Closure support

A way to use Fehler with closures (#[throws] || {...}) would be nice to have.

Unreachable expression warnings with panics

The todo!() macro produces an unreachable expression warning:

#[throws(Error)]
fn foo() {
    todo!()
}
warning: unreachable expression
 --> rocket_lang/src/main.rs:4:1
  |
4 | #[throws(Error)]
  | ^^^^^^^^^^^^^^^^ unreachable expression
5 | fn foo() {
6 |     todo!()
  |     ------- any code following this expression is unreachable
  |
  = note: `#[warn(unreachable_code)]` on by default
  = note: this warning originates in the attribute macro `throws` (in Nightly builds, run with -Z macro-backtrace for more info)

It'd be nice if this were be fixed if possible.

`throws` for async trait functions?

I might be doing something wrong here but I had an async trait function:

#[tonic::async_trait]
impl EventStore for EventStoreService {
    #[throws(Status)]
    async fn emit(
        &self,
        request: Request<EventRequest>,
    ) -> Response<EventResponse> {
        Response::new(EventResponse { data: "hello world".into() })
    }
}

and the compiler throws up on me

error[E0053]: method `emit` has an incompatible type for trait
  --> src/grpc/server.rs:27:5
   |
27 |     #[throws(Status)]
   |     ^^^^^^^^^^^^^^^^^ expected struct `std::pin::Pin`, found enum `std::result::Result`
   |
  ::: /Users/me/project/target/debug/build/project-4065396c784b1521/out/event_store.rs:72:5
   |
72 |     #[async_trait]
   |     -------------- type in trait
   |
   = note: expected fn pointer `fn(&'life0 grpc::server::EventStoreService, tonic::request::Request<_>) -> std::pin::Pin<std::boxed::Box<(dyn core::future::future::Future<Output = std::result::Result<tonic::response::Response<grpc::server::event_store::EventResponse>, tonic::status::Status>> + std::marker::Send + 'async_trait)>>`
              found fn pointer `fn(&'life0 grpc::server::EventStoreService, tonic::request::Request<_>) -> std::result::Result<std::pin::Pin<std::boxed::Box<(dyn core::future::future::Future<Output = tonic::response::Response<grpc::server::event_store::EventResponse>> + std::marker::Send + 'async_trait)>>, tonic::status::Status>`

error[E0271]: type mismatch resolving `<std::result::Result<std::pin::Pin<std::boxed::Box<dyn core::future::future::Future<Output = tonic::response::Response<grpc::server::event_store::EventResponse>> + std::marker::Send>>, tonic::status::Status> as fehler::__internal::_Succeed>::Ok == std::pin::Pin<std::boxed::Box<impl core::future::future::Future>>`
  --> src/grpc/server.rs:27:5
   |
27 |     #[throws(Status)]
   |     ^^^^^^^^^^^^^^^^^ expected trait `core::future::future::Future`, found opaque type
   |
   = note: expected type `std::pin::Pin<std::boxed::Box<dyn core::future::future::Future<Output = tonic::response::Response<grpc::server::event_store::EventResponse>> + std::marker::Send>>`
            found struct `std::pin::Pin<std::boxed::Box<impl core::future::future::Future>>`

This might have to do with the fact that the expected type is a Pin?

This compiles no problem:

#[tonic::async_trait]
impl EventStore for EventStoreService {
    async fn emit(
        &self,
        request: Request<EventRequest>,
    ) -> Result<Response<EventResponse>, Status> {
        Ok(Response::new(EventResponse { data: "hello world".into() }))
    }
}

Feel free to respond with the old "good luck" and close the issue if this is specific to tonic.

Side note:
Found this crate in your last couple of blog posts, keep fighting the good fight, it'll be worth it for all of us in the end!

Consider supporting lazily constructed context on Result or context on Exception

The fehler::Context::context impl on Result looks like a performance hazard for context that is potentially expensive to construct.

use fehler::{throws, Context};
use std::path::Path;

#[throws]
fn f(_path: &Path) {}

fn main() {
    let path = Path::new("/");
    let _ = f(path).context(format!("failed to f {}", path.display()));
}

Would you consider something like f(path).with_context(|| ...) or f(path).map_err(|ex| ex.context(...))?

Type of throw! is not !

I believe the following should compile. It does if you replace throw! with a return or panic.

use fehler::{error, throw, throws};

#[throws]
fn f(b: bool) {
    let x;
    if b {
        x = 0;
    } else {
        throw!(error!("it fehled"));
    }
    dbg!(x);
}

As of 1.0.0-alpha.0 it fails with:

error[E0381]: use of possibly-uninitialized variable: `x`
  --> src/main.rs:11:10
   |
11 |     dbg!(x);
   |          ^ use of possibly-uninitialized `x`

Consider handing over ownership of the crate name to the "culpa" contributors

I think it's fair to say that fehler has been a success. Apparently (#66) aren't able to work on this right now, which is sad.

The way that cargo works means that it is quite disruptive to change the crate name. (It's particularly annoying for downstreams like Debian who curate and do release management.)

Would you consider granting co-ownership of the crate name fehler (and fehler-macros) on crates.io to @Nemo157? (I'm also willing to be a crate owner to help reduce bus factor but I don't have tuits right now for reviewing work on fehler.)

catch macro?

Are there any plans for adding a catch! macro that would allow doing something like

let result: Result<i64> = catch!{
  let y = f1()?;
  let z = f2()?;
  if cond(z) {
    throw!(SomeError);
  }
  y + z
};

The simplest way to do this would be to add support for closures to the throws macro, and add a catch! macro like:

macro_rules! catch {
  ($($tts:tt)*) => { (#[throws(_)] || {$($tts)*})() };
}

But it isn't perfect, since a return inside the catch would behave in an unexpected way (return from the hidden closure instead of the outer function) and lifetimes might cause issues as well.

A better solution would be to make catch! a procedural macro that wraps the body in labeled scope, and transforms any throw! or ? to use a labeled break with value instead of a return. And wraps the final value of the expression. I'm not sure how feasible that is.

compiler errors sometimes made nugatory (closures, struct derive?)

Hi. Thanks again for this excellent package.

To reproduce:

#[allow(unused_imports)]
use fehler::throws;

fn parse_args(_f: &dyn Fn(&mut ())) { }

#[throws(std::io::Error)]
fn main() 
{
    #[derive(Default,Debug)]
    struct Args { }
    let args = parse_args(&|ma|{ });
    let spec = 42;
}

And run cargo build, with fehler="1" in your dependencies.

Actual output:

warning: unused variable: `ma`
  |
  = note: `#[warn(unused_variables)]` on by default

warning: unused variable: `args`

warning: unused variable: `spec`

warning: 3 warnings emitted

    Finished dev [unoptimized + debuginfo] target(s) in 0.01s

Note the very unhelpful error messages, with no location information and no suggestions.

Expected output:


warning: unused variable: `ma`
  --> src/bin/report.rs:11:29
   |
11 |     let args = parse_args(&|ma|{ });
   |                             ^^ help: if this is intentional, prefix it with an underscore: `_ma`
   |
   = note: `#[warn(unused_variables)]` on by default

warning: unused variable: `args`
  --> src/bin/report.rs:11:9
   |
11 |     let args = parse_args(&|ma|{ });
   |         ^^^^ help: if this is intentional, prefix it with an underscore: `_args`

warning: unused variable: `spec`
  --> src/bin/report.rs:12:9
   |
12 |     let spec = 42;
   |         ^^^^ help: if this is intentional, prefix it with an underscore: `_spec`

warning: 3 warnings emitted

    Finished dev [unoptimized + debuginfo] target(s) in 0.17s

This rather odd test case was minimised from a more complicated program. Empirically, the issue goes away if I remove any of (a) #[throws] (b) the #[derive] on the struct (c) the closure. The problem can occur with errors as well as warnings. In one particular version of my actual program I got the very unhelpful output type annotations needed without any indication of where!

Thanks for your attention.

could not find `fehler` in `{{root}}` when reexporting the lib

use other_lib::fehler::{self, *}

#[throws]
fn main() {
}
error[E0433]: failed to resolve: could not find `fehler` in `{{root}}`
   --> super_app/src/main.rs:5:5
    |
5 |     #[throws]
    |     ^^^^^^^^^ could not find `fehler` in `{{root}}`
    |
    = note: this error originates in an attribute macro (in Nightly builds, run with -Z macro-backtrace for more info)

when it's exported from another crate other_lib like:

pub use fehler;

Is it a bug or a feature?

License?

Hi! I don't seem to see any license mentioned in the repo? It might make it hard for people to know if they can use this library in their projects...

Spurious unecessary trailing semicolon warning

I'm getting a warning to remove a trailing semicolon in a trait definition, but removing it causes an error:

pub(crate) trait CommandExt {
  #[throws]
  fn out(&mut self) -> String;

  #[throws]
  fn status_into_result(&mut self); // <-- this semicolon triggers the warning
}
warning: unnecessary trailing semicolon
 --> bin/gen/src/command_ext.rs:8:35
  |
8 |   fn status_into_result(&mut self);
  |                                   ^ help: remove this semicolon
  |

Are there any plans to support "x86_64-unknown-linux-musl"?

When I build my project under alpine linux I get this message:

error: cannot produce proc-macro for `fehler-macros v1.0.0` as the target `x86_64-unknown-linux-musl` does not support these crate types

Please suggest if there is any workaround.
Thank you.

Where did Exception go?

I really liked Exception. Why was it removed in #26? Was it moved to another create? If not can I at least have it behind a feature flag if the goal is to make it more lightweight?

There also seems to be a new crate because of this change called fehler_more. Which just exports everything from the old version of the fehler crate. That's obviously terrible but proves that other people want to have Exception, etc. as well.

The immediate fix would be to simply stick with fehler = "1.0.0-alpha.1"

#[throws] panics if function contains a "fn" type alias

Example code:

use fehler::throws;
use std::io::Error;

#[throws]
fn my_function() -> Vec<u8> {
    type Foo = fn(usize);
    std::fs::read("blahblah")?
}

fn main() {
    my_function().unwrap();
}

Compilation fails with:

error: custom attribute panicked
 --> src/main.rs:4:1
  |
4 | #[throws]
  | ^^^^^^^^^
  |
  = help: message: Wrapper type must be a normal path type
$ rustc --version
rustc 1.43.0 (4fb7144ed 2020-04-20)

no_std support?

Can der Fehler, in principle (I see it's not implemented right now), work with #![no_std] and without alloc?

throws inserts method body for trait methods

This code:

trait Example {
    #[throws]
    fn foo();
}

expands to this:

trait Example {
    fn foo() -> ::core::result::Result<(), Error> {
        <_ as ::fehler::__internal::_Succeed>::from_ok(())
    }
}

This inserts a default method for the trait which has two annoying effects:

  1. Complaints about an extra semi-colon
  2. No complaints in trait implementations that don't provide what should be a required method.

This is using rust 1.42.0 with fehler 1.0.0.

The problem appears to be in Throws::fold because the trait method is parsed by parse for ImplItemMethod. The implementation of this function in syn includes this comment:

                // Accept methods without a body in an impl block because
                // rustc's *parser* does not reject them (the compilation error
                // is emitted later than parsing) and it can be useful for macro
                // DSLs.

which causes this problem.

top-level context printing

When an exception is propagated all the way to the top-level of the crate, it's useful to print the list of causes. For failure this behavior exists in exitfailure, but it'd be nice if fehler::Exception would support this too.

I believe this can be implementing using the std::process:Termination trait.

Example Implementation

impl std::process::Termination for Exception {
    fn report(self) -> i32 {
        for err in self.errors() {
            eprintln!("{}", err);
        }
        1
    }
}

Design Considerations

Is there anything we should do about backtraces? Something akin to colorize-backtrace for debug builds perhaps? It may also just be out of scope for this crate (as it mostly seems to emphasize core functionality).

Should we have a derive for Display?

Implementing Display is actually the most annoying part of defining your own error. Should der Fehler provide a derive to help you with this?

One option is one of the relatively "obvious" less opinionated derives, like the one found in the display derive crate I wrote for failure. Another option is to do something fancy like deriving the display from doc comments.

Fehler didn't support returning `Result<Box<dyn Trait>, _>`

Code:

use fehler::throws;

trait FooTrait {}
struct FooStruct;
struct FooError;
impl FooTrait for FooStruct {}
#[throws(FooError)]
fn foo() -> Box<dyn FooTrait> {
    Box::new(FooStruct)
}

Compile result:

Error[E0271]: type mismatch resolving ...
    |
104 | #[throws(FooError)]
    | ^^^^^^^^^^^^^^^^^^^^^^ expected trait object `dyn mymodule::FooTrait`, found struct `mymodule::FooStruct`
    |
    = note: expected type `std::boxed::Box<dyn mymodule::FooTrait>`
             found struct `std::boxed::Box<mymodule::FooStruct>`
    = note: this error originates in an attribute macro (in Nightly builds, run with -Z macro-backtrace for more info)

Does using #[throws] impact compile times?

I've used fehler on a few small projects and like it very much. (Thank you!)

I'm considering using it for a larger project, but I'm worried that having the #[throws] macros on a bunch of functions in a large codebase may increase compile times, just from having all functions be filtered through a proc-macro invocation.

Is this something I should be worried about?

Support throwing closures

#![feature(stmt_expr_attributes, proc_macro_hygiene)]

type Error = usize;

fn main() {
    let x = #[fehler::throws] || 5;
    
    dbg!(x());
}

I expected this to work and print Ok(5), with an expansion something like

    let x = || Result<_, Error> { Ok({ 5 }) };

`return` statements in `else` branch of `let-else` do not get wrapped

#[throws(Error)]
fn foo(a: Option<u8>) -> u8 {
    let Some(a) = a else {
        return 0;
    };
    a
}

does not work.

You need to do

#[throws(Error)]
fn foo(a: Option<u8>) -> u8 {
    let Some(a) = a else {
        return Ok(0);
    };
    a
}

to make it compile.

In contrast, there is no need for this Ok(...) if you use if let - else:

#[throws(Error)]
fn foo(a: Option<u8>) -> u8 {
    if let Some(a) = a {
        a
    } else {
        return 0;
    }
}

But I mean, let-else breaks even rustfmt (I had to manually format the code) and github syntax highlighting (else in the first code snippet does not get highlighted as a keyword, return does not get highlighted in the first two), so yeah, a breakage in a 2yo procmacro is not really surprising. (Wonder if using a separate keyword like guard would be a better decision or having to bump the edition again would be to much of a hustle.)

Anyways, nice article (and the whole blog too):
https://without.boats/blog/why-ok-wrapping/
Btw, do you happen to know if any progress has been made on this feature in rust proper over the last two years since the blogpost? I, personally, would love to see this in Rust, but from what I can tell, it's a bit controversial with some people and definitely does not seem to be a high priority for the language right now.

Unreachable expression warning in tail throw

Thanks for this crate! Minor annoyance I stumbled upon:

Given a function:

#[throws(())]
fn always_throw() {
    throw!(());
}

A compile warning pops out:

   | #[throws(())]
   | ^^^^^^^^^^^^^
   | |
   | unreachable expression
   | any code following this expression is unreachable

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.