Giter Club home page Giter Club logo

guard's Introduction

guard

Travis CI

This feature has finally been officially added to Rust. This crate should be considered deprecated once #![feature(let_else)] is stabilized.

This crate exports a macro which implements most of RFC 3137 (originally RFC 1303), a "let-else" or "guard" expression as you can find in Swift.

The syntax decided upon in the RFC is let PAT = EXPR else { BODY } (where BODY must diverge). This macro understands mostly what has been implemented in the compiler, with a few limitations detailed below, as well as a variation proposed in the first RFC with the else clause in the middle.

The crate also implements a variant guard_unwrap that panics if the match fails.

Examples

#[macro_use] extern crate guard;
use std::env;

fn main() {
    // read configuration from a certain environment variable
    // do nothing if the variable is missing
    guard!(let Ok(foo) = env::var("FOO") else { return });

    println!("FOO = {}", foo);
}

Cargo features

  • nightly was historically required to avoid warnings when compiling with a nightly compiler. It now does nothing and is kept around only for backwards-compatibility purposes.
  • debug enables trace_macros for debugging. Requires a nightly compiler (but not this crate's nightly feature).

How it works

It's difficult to implement this behavior as a macro, because a let statement must be created in the enclosing scope. Besides that, it is desirable to avoid the necessity of repeating the identifiers bound by the pattern. The strategy used here is to scan the pattern for identifiers, and use that to construct a top-level let statement which internally uses a match to apply the pattern. This scanning is almost possible -- see limitations #1 and #2 below.

This strategy also means that PAT needs to be input to the macro as an unparsed sequence of token trees. There are two ways to take an unbounded sequence of token trees as input without causing ambiguity errors: put the token trees at the end (my current choice) or enclose them in brackets. Originally, this choice resulted in a backwards invocation syntax. Since version 0.2.0, more convenient syntaxes are supported by adopting a two-pass parsing strategy: the macro essentially takes its entire input as a sequence of tokens, splits on = and else, then parses the results again.

There are a number of subtleties in the expansion to avoid various warning and pitfalls; see the macro source for more details.

Limitations

  1. Expressions in the pattern are not supported. This is a limitation of the current Rust macro system -- I'd like to say "parse an identifier in this position, but if that fails try parsing an expression" but this is is impossible; I can only test for specific identifiers. It's easy to get around this restriction: use a pattern guard (as in match) instead.
  2. Empty, un-namespaced enum variants and structs cause the expansion to fail, because the macro thinks they are identifiers. It's possible to get around this as well, though an open PR is aiming to take away the easiest workaround:
    • For empty enum variants, use Empty(..) until #29383 turns into an error, after that include the enum name as in Enum::Empty. (For now you will get a warning.)
    • For unit-like structs, use Empty(..) until #29383 turns into an error, after that namespace it as in namespace::Empty, or use Empty{} (requires #![feature(braced_empty_structs)]). (For now you will get a warning.)
    • Of course you can also use a path to reference the variant or struct, though this may be impossible (if it's local to a function/block) or inconvenient (if it was imported from another module or crate).
  3. PAT cannot be irrefutable. This is the same behavior as if let and match, and it's useless to write a guard with an irrefutable pattern anyway (you can just use let), so this shouldn't be an issue. This is slightly more annoying than it could be due to limitation #1. Nonetheless, if #14252 is ever fixed, irrefutable patterns could be allowed by inserting a no-op pattern guard into the expansion.

Differences with the rustc implementation

  • Parentheses around or-patterns are not necessary
  • Cannot use a ref binding to a non-copy value (you'll get a borrowck error)

guard's People

Contributors

durka avatar soenkehahn avatar

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

Watchers

 avatar  avatar  avatar

guard's Issues

Guard does not build anymore on 1.70

Heya, I don't know if you maintain this much anymore, but it fails to build on 1.70: Rust removed support for box_syntax (rust-lang/rust#108471).

guard only uses it in a single test gated behind cfg(nightly) so it should not be an issue, but it is:

❯ git clone https://github.com/durka/guard/
[...]
❯ cd guard
❯ cargo b
   Compiling guard v0.5.1 (~/guard)
error: `box_syntax` has been removed
   --> src/lib.rs:426:20
    |
426 |         let foo = (box 42, [1, 2, 3]);
    |                    ^^^^^^
    |
help: use `Box::new()` instead
    |
426 |         let foo = (Box::new(42), [1, 2, 3]);
    |                    ~~~~~~~~~~~~

error: `box_syntax` has been removed
   --> src/lib.rs:430:29
    |
430 |         let mut foo = Some((box 42, [1, 2, 3]));
    |                             ^^^^^^
    |
help: use `Box::new()` instead
    |
430 |         let mut foo = Some((Box::new(42), [1, 2, 3]));
    |                             ~~~~~~~~~~~~

error: `box_syntax` has been removed
   --> src/lib.rs:446:20
    |
446 |         let foo = (box 42, [1, 2, 3]);
    |                    ^^^^^^
    |
help: use `Box::new()` instead
    |
446 |         let foo = (Box::new(42), [1, 2, 3]);
    |                    ~~~~~~~~~~~~

error: `box_syntax` has been removed
   --> src/lib.rs:451:20
    |
451 |         let foo = (box 42, [1, 2, 3]);
    |                    ^^^^^^
    |
help: use `Box::new()` instead
    |
451 |         let foo = (Box::new(42), [1, 2, 3]);
    |                    ~~~~~~~~~~~~

error: could not compile `guard` (lib) due to 4 previous errors

Relicense under dual MIT/Apache-2.0

Why?

The MIT license requires reproducing countless copies of the same copyright
header with different names in the copyright field, for every MIT library in
use. The Apache license does not have this drawback, and has protections from
patent trolls and an explicit contribution licensing clause. However, the
Apache license is incompatible with GPLv2. This is why Rust is dual-licensed as
MIT/Apache (the "primary" license being Apache, MIT only for GPLv2 compat), and
doing so would be wise for this project. This also makes this crate suitable
for inclusion in the Rust standard distribution and other project using dual
MIT/Apache.

How?

To do this, get explicit approval from each contributor of copyrightable work
(as not all contributions qualify for copyright) and then add the following to
your README:

## License

Licensed under either of
 * Apache License, Version 2.0 ([LICENSE-APACHE](LICENSE-APACHE) or http://www.apache.org/licenses/LICENSE-2.0)
 * MIT license ([LICENSE-MIT](LICENSE-MIT) or http://opensource.org/licenses/MIT)
at your option.

### Contribution

Unless you explicitly state otherwise, any contribution intentionally submitted
for inclusion in the work by you shall be dual licensed as above, without any
additional terms or conditions.

and in your license headers, use the following boilerplate (based on that used in Rust):

// Copyright (c) 2015 t developers
// Licensed under the Apache License, Version 2.0
// <LICENSE-APACHE or
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT
// license <LICENSE-MIT or http://opensource.org/licenses/MIT>,
// at your option. All files in the project carrying such
// notice may not be copied, modified, or distributed except
// according to those terms.

And don't forget to update the license metadata in your Cargo.toml!

Contributor checkoff

enhance the tests

Currently the tests are basically "make sure this compiles". But they don't check that the identifiers were bound to the correct values.

Also, I should probably use compiletest to make sure the correct errors are generated from malformed patterns and non-diverging bodies.

better invocation syntax

=> is never legal inside a pattern (right?) so I should be able to rearrange the syntax such that the pattern goes first, and when the recursion encounters => it can split off and parse the rest of the parameters. |-handling could be a snag.

Less of a hack than the above would be syntax which encloses the pattern in brackets.

Consider output of guard_unwrap

It is definitely possible to get better printing, but after playing with it I agree with you that it's better left to a later decision. I'll explain why.

The way the macro works is it calls itself over and over while deconstructing the expression, and finally calls $crate::__guard_output with all the pieces nicely separated. The way to customize the panic message is to swap out this output macro with a different one. This is the easy part and can be done by threading another argument all the way through the macro, so we can specify from the beginning the alternative output macro.

But there's another problem when printing: you can't assume "{:?}" will work 😞
You can print out stringified versions of the pattern and expression that didn't match, but reliably printing the debug format of the expression, or "<unknown>" if it doesn't even impl Debug, requires the unstable specialization feature (see debugit). (NB: this is a type system limitation, not a macro limitation, so it can't even be solved via a procedural macro.)

Originally posted by @durka in #8 (comment)

Feature request: bigger enclosing block to use guards in bulk.

I want to write something like

guard_block! {
    let Some(b) = a else { return; }
    let Some(c) = b else { return; }
    foo(&c);
    let Some(d) = c else { return; }
}

instead of encasing everything in additional level of parenthesis:

guard!(let Some(b) = a else { return; });
guard!(let Some(c) = b else { return; });
foo(&c);
guard!(let Some(d) = c else { return; });

guard_block! { guard let Some(b) = a else { return; } } is fine too.

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.