├── .gitignore ├── tryvial-proc ├── Cargo.toml └── src │ └── lib.rs ├── Cargo.toml ├── .github └── workflows │ └── ci.yml ├── README.md └── src └── lib.rs /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | /Cargo.lock 3 | /tryvial-proc/Cargo.lock 4 | -------------------------------------------------------------------------------- /tryvial-proc/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "tryvial-proc" 3 | version = "0.2.0" 4 | edition = "2021" 5 | 6 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 7 | 8 | description = "Procedural macro for the `tryvial` crate" 9 | repository = "https://github.com/JoJoJet/tryvial" 10 | license = "MIT OR Apache-2.0" 11 | keywords = [] 12 | categories = [] 13 | 14 | [lib] 15 | proc-macro = true 16 | 17 | [dependencies] 18 | proc-macro2 = "1" 19 | quote = "1" 20 | venial = "0.5.0" 21 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "tryvial" 3 | version = "0.2.0" 4 | edition = "2021" 5 | 6 | description = "Small crate for ok-wrapping and try blocks" 7 | repository = "https://github.com/JoJoJet/tryvial" 8 | license = "MIT OR Apache-2.0" 9 | keywords = ["try", "catch", "ok", "wrap", "throw"] 10 | categories = ["rust-patterns", "no-std"] 11 | 12 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 13 | 14 | [dependencies] 15 | tryvial-proc = { path = "tryvial-proc", version = "0.2.0", optional = true } 16 | 17 | [features] 18 | default = ["proc-macro"] 19 | proc-macro = ["dep:tryvial-proc"] 20 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | on: [push, pull_request] 2 | 3 | name: CI 4 | 5 | jobs: 6 | 7 | test: 8 | name: Test Suite 9 | runs-on: windows-latest 10 | steps: 11 | - uses: actions/checkout@v2 12 | - uses: actions-rs/toolchain@v1 13 | with: 14 | profile: minimal 15 | toolchain: stable 16 | override: true 17 | - uses: actions-rs/cargo@v1 18 | with: 19 | command: test 20 | 21 | fmt: 22 | name: Rustfmt 23 | runs-on: windows-latest 24 | steps: 25 | - uses: actions/checkout@v2 26 | - uses: actions-rs/toolchain@v1 27 | with: 28 | profile: minimal 29 | toolchain: stable 30 | override: true 31 | - run: rustup component add rustfmt 32 | - uses: actions-rs/cargo@v1 33 | with: 34 | command: fmt 35 | args: --all -- --check 36 | 37 | clippy: 38 | name: Clippy 39 | runs-on: windows-latest 40 | steps: 41 | - uses: actions/checkout@v2 42 | - uses: actions-rs/toolchain@v1 43 | with: 44 | profile: minimal 45 | toolchain: stable 46 | override: true 47 | - run: rustup component add clippy 48 | - uses: actions-rs/cargo@v1 49 | with: 50 | command: clippy 51 | args: -- -D warnings 52 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # `tryvial` 2 | 3 | 4 | 5 | A small crate for Ok-wrapping and try blocks. 6 | This is compatible with [`Result`](https://doc.rust-lang.org/stable/core/result/enum.Result.html), [`Option`](https://doc.rust-lang.org/stable/core/option/enum.Option.html), 7 | and any type implementing the unstable [`std::ops::Try`](https://doc.rust-lang.org/std/ops/trait.Try.html) trait. 8 | 9 | *This crate does not require nightly Rust.* 10 | 11 | ## Overview 12 | 13 | The macro `try_fn` is used to perform Ok-wrapping on the return value of a function. 14 | 15 | Before: 16 | ```rust 17 | fn main() -> Result<(), Box> { 18 | println!("Enter your name: "); 19 | let mut name = String::new(); 20 | std::io::stdin().read_line(&mut name)?; 21 | println!("Hello, {name}!"); 22 | Ok(()) // this is ugly 23 | } 24 | ``` 25 | 26 | After: 27 | ```rust 28 | #[try_fn] 29 | fn main() -> Result<(), Box> { 30 | println!("Enter your name: "); 31 | let mut name = String::new(); 32 | std::io::stdin().read_line(&mut name)?; 33 | println!("Hello, {name}!"); 34 | } 35 | ``` 36 | 37 | --- 38 | 39 | The macro [`try_block`](https://docs.rs/tryvial/latest/tryvial/macro.try_block.html) is an implementation of "try blocks" from nightly rust. 40 | 41 | ```rust 42 | let result: Result = try_block! { 43 | let a = do_one(x)?; 44 | let b = do_two(a)?; 45 | b 46 | }; 47 | ``` 48 | 49 | --- 50 | 51 | The macro [`wrap_ok`](https://docs.rs/tryvial/latest/tryvial/macro.wrap_ok.html) simply wraps an expression with the "ok" variant for a given `Try` type. 52 | 53 | ```rust 54 | assert_eq!(Some(42), wrap_ok!(42)); 55 | ``` 56 | 57 | 58 | 59 | ## License 60 | 61 | MIT or Apache-2.0 62 | -------------------------------------------------------------------------------- /tryvial-proc/src/lib.rs: -------------------------------------------------------------------------------- 1 | use proc_macro::TokenStream; 2 | use proc_macro2::TokenStream as TokenStream2; 3 | use quote::quote; 4 | use venial::{Declaration, Error, Function}; 5 | 6 | /// An attribute macro that performs "Ok-wrapping" on the return value of a `fn` item. 7 | /// This is compatible with [`Result`], [`Option`], [`ControlFlow`], and any type that 8 | /// implements the unstable [`std::ops::Try`] trait. 9 | /// 10 | /// Using this macro is equivalent to wrapping the body of a fn in a try block. 11 | /// 12 | /// Nightly: 13 | /// ```ignore 14 | /// fn fallible_fn(x: T) -> Result { 15 | /// try { 16 | /// let a = do_one(x)?; 17 | /// let b = do_two(a)?; 18 | /// b 19 | /// } 20 | /// } 21 | /// ``` 22 | /// 23 | /// With `try_fn`: 24 | /// ```ignore 25 | /// #[try_fn] 26 | /// fn fallible_fn(x: T) -> Result { 27 | /// let a = do_one(x)?; 28 | /// let b = do_two(a)?; 29 | /// b 30 | /// } 31 | /// ``` 32 | /// 33 | /// [`ControlFlow`]: core::ops::ControlFlow 34 | #[proc_macro_attribute] 35 | pub fn try_fn(_attr: TokenStream, item: TokenStream) -> TokenStream { 36 | impl_try_fn(item.into()) 37 | .unwrap_or_else(|e| e.to_compile_error()) 38 | .into() 39 | } 40 | 41 | #[proc_macro_attribute] 42 | #[deprecated(note = "renamed to `try_fn`")] 43 | pub fn tryvial(_attr: TokenStream, item: TokenStream) -> TokenStream { 44 | impl_try_fn(item.into()) 45 | .unwrap_or_else(|e| e.to_compile_error()) 46 | .into() 47 | } 48 | 49 | fn impl_try_fn(input: TokenStream2) -> Result { 50 | let decl = venial::parse_declaration(input)?; 51 | let Function { 52 | attributes, 53 | vis_marker, 54 | qualifiers, 55 | tk_fn_keyword, 56 | name, 57 | generic_params, 58 | tk_params_parens: _, 59 | params, 60 | where_clause, 61 | tk_return_arrow: _, 62 | return_ty, 63 | tk_semicolon: _, 64 | body, 65 | } = match decl { 66 | Declaration::Function(item) => item, 67 | _ => Err(Error::new("`#[try_fn]` is supported only on `fn` items"))?, 68 | }; 69 | 70 | let body = body.ok_or(Error::new( 71 | "`#[try_fn]` can only be used on functions with a body", 72 | ))?; 73 | 74 | let return_ty = return_ty.map_or_else(|| quote! { () }, |ty| quote! { #ty }); 75 | 76 | Ok(quote! { 77 | #(#attributes)* 78 | #vis_marker #qualifiers #tk_fn_keyword #name #generic_params ( #params ) -> #return_ty 79 | #where_clause 80 | { 81 | ::core::iter::empty().try_fold(#body, |_, __x: ::core::convert::Infallible| match __x {}) 82 | } 83 | }) 84 | } 85 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | //! A small crate for Ok-wrapping and try blocks. 2 | //! This is compatible with [`Result`](::core::result::Result), [`Option`](::core::option::Option), 3 | //! and any type implementing the unstable [`std::ops::Try`](https://doc.rust-lang.org/std/ops/trait.Try.html) trait. 4 | //! 5 | //! *This crate does not require nightly Rust.* 6 | //! 7 | //! # Overview 8 | //! 9 | //! The macro `try_fn` is used to perform Ok-wrapping on the return value of a function. 10 | //! 11 | //! Before: 12 | //! ``` 13 | //! fn main() -> Result<(), Box> { 14 | //! println!("Enter your name: "); 15 | //! let mut name = String::new(); 16 | //! std::io::stdin().read_line(&mut name)?; 17 | //! println!("Hello, {name}!"); 18 | //! Ok(()) // this is ugly 19 | //! } 20 | //! ``` 21 | //! 22 | //! After: 23 | //! ``` 24 | //! # use tryvial::try_fn; 25 | //! #[try_fn] 26 | //! fn main() -> Result<(), Box> { 27 | //! println!("Enter your name: "); 28 | //! let mut name = String::new(); 29 | //! std::io::stdin().read_line(&mut name)?; 30 | //! println!("Hello, {name}!"); 31 | //! } 32 | //! ``` 33 | //! 34 | //! --- 35 | //! 36 | //! The macro [`try_block`](crate::try_block) is an implementation of "try blocks" from nightly rust. 37 | //! 38 | //! ``` 39 | //! # use tryvial::try_block; 40 | //! # type T = (); type E = (); 41 | //! # fn do_one((): T) -> Result { Ok(()) } 42 | //! # fn do_two((): T) -> Result { Ok(()) } 43 | //! # let x = (); 44 | //! let result: Result = try_block! { 45 | //! let a = do_one(x)?; 46 | //! let b = do_two(a)?; 47 | //! b 48 | //! }; 49 | //! ``` 50 | //! 51 | //! --- 52 | //! 53 | //! The macro [`wrap_ok`](crate::wrap_ok) simply wraps an expression with the "ok" variant for a given `Try` type. 54 | //! 55 | //! ``` 56 | //! # use tryvial::wrap_ok; 57 | //! assert_eq!(Some(42), wrap_ok!(42)); 58 | //! ``` 59 | 60 | #![no_std] 61 | 62 | #[cfg(feature = "proc-macro")] 63 | pub use tryvial_proc::{try_fn, tryvial}; 64 | 65 | /// Performs "Ok-wrapping" on the result of an expression. 66 | /// This is compatible with [`Result`], [`Option`], [`ControlFlow`], and any type that 67 | /// implements the unstable [`std::ops::Try`] trait. 68 | /// 69 | /// The destination type must be specified with a type ascription somewhere. 70 | #[macro_export] 71 | macro_rules! wrap_ok { 72 | ($e:expr) => { 73 | ::core::iter::empty().try_fold($e, |_, __x: ::core::convert::Infallible| match __x {}) 74 | }; 75 | } 76 | 77 | /// Macro for the receiving end of a `?` operation. 78 | /// 79 | /// ``` 80 | /// # use tryvial::try_block; 81 | /// // Note: this fails without explicitly specifying the error type. 82 | /// let y: Result<_, std::num::ParseIntError> = try_block! { 83 | /// "1".parse::()? + "2".parse::()? 84 | /// }; 85 | /// assert_eq!(y, Ok(3)); 86 | /// ``` 87 | #[macro_export] 88 | macro_rules! try_block { 89 | { $($token:tt)* } => { 90 | (|| $crate::wrap_ok!({ 91 | $($token)* 92 | }))() 93 | } 94 | } 95 | 96 | #[cfg(test)] 97 | extern crate alloc; 98 | 99 | #[cfg(test)] 100 | mod tests { 101 | use super::*; 102 | use core::ops::ControlFlow; 103 | 104 | /// This is a doc comment. 105 | #[try_fn] 106 | /// And another one. 107 | pub fn with_doc_comments() -> ControlFlow { 108 | ControlFlow::Break(11)?; 109 | } 110 | 111 | #[test] 112 | fn test_with_doc() { 113 | assert!(matches!(with_doc_comments(), ControlFlow::Break(11))); 114 | } 115 | 116 | #[try_fn] 117 | unsafe fn generic_fn(x: T, y: &U) -> ControlFlow 118 | where 119 | T: PartialEq, 120 | { 121 | if x == *y { 122 | ControlFlow::Break(y.clone())?; 123 | } 124 | } 125 | 126 | #[test] 127 | fn test_generic_fn() { 128 | use alloc::borrow::ToOwned; 129 | match unsafe { generic_fn("Hello, world", &"Hello, world".to_owned()) } { 130 | ControlFlow::Break(s) => assert_eq!(s, "Hello, world"), 131 | ControlFlow::Continue(()) => unreachable!(), 132 | } 133 | } 134 | 135 | struct MyStruct(u32); 136 | 137 | impl core::convert::TryFrom<&str> for MyStruct { 138 | type Error = core::num::ParseIntError; 139 | #[try_fn] 140 | fn try_from(value: &str) -> Result { 141 | Self(value.parse()?) 142 | } 143 | } 144 | 145 | #[test] 146 | fn test_parse() { 147 | assert!(matches!("34".try_into(), Ok(MyStruct(34)))); 148 | } 149 | } 150 | --------------------------------------------------------------------------------