1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102
//! Procedural macro polyfill for Ok-wrapping functions. //! //! [Try blocks](https://doc.rust-lang.org/unstable-book/language-features/try-blocks.html) //! are a nightly rust feature for introducing less-than-a-function boundary `?` //! operator. One notable feature of try-blocks as currently implemented is //! ok-wrapping: //! //! ```not-rust //! let x: Option<i32> = try { 92 }; //! assert!(x.is_some()); //! ``` //! //! That is, `try { expr }` desugars to, roughtly, `(|| Try::from_ok(expr))()`. //! //! **Crucially** you still have to use `?` to propagate erors, but you don't //! have to wrap the final value in `Ok`. Moreover, if the final value is //! already a `Result`, you need to add an extra `?`, to make the errors even //! more explicit than without ok-wrapping. //! //! The try-block syntax naturarly generalizes to functions: //! //! ```not-rust //! fn word_count(path: &Path) -> io::Result<usize> try { //! let mut res = 0; //! let file = fs::File::open(path)?; //! let mut reader = io::BufReader::new(file); //! for line in reader.lines() { //! let line = line?; //! res += line.split_whitespace().count(); //! } //! res //! } //! ``` //! //! Unfortunatelly, we can't have exact that even with proc-macros, but this //! crate provides something close enough: //! //! ``` //! # use std::{path::Path, fs, io::{self, BufRead}}; //! use versuch::try_fn; //! //! ##[try_fn] //! fn word_count(path: &Path) -> io::Result<usize> { //! let mut res = 0; //! let file = fs::File::open(path)?; //! let mut reader = io::BufReader::new(file); //! for line in reader.lines() { //! let line = line?; //! res += line.split_whitespace().count(); //! } //! res //! } //! ``` //! //! This crate is very much inspired by the [https://crates.io/crates/fehler](`fehler`) crate. //! //! Disclaimer: this crate is a proc macro and thus can measurably increase the //! number of dependencies and compile times. It also breaks IDE support (sorry //! for that, @matklad). extern crate proc_macro; use proc_macro::TokenStream; use syn::{parse_macro_input, ItemFn}; /// Wraps the body of the function into `Ok` or `Some`. #[proc_macro_attribute] pub fn try_fn(args: TokenStream, input: TokenStream) -> TokenStream { assert!(args.is_empty()); let syn::ItemFn { attrs, vis, sig, block, } = parse_macro_input!(input as ItemFn); let ok = ok(&sig).unwrap_or_else(|| panic!("only `Result` and `Option` are supported")); let ok = quote::format_ident!("{}", ok); TokenStream::from(quote::quote! { #(#attrs)* #vis #sig { #ok(#block) } }) } fn ok(sig: &syn::Signature) -> Option<&'static str> { let path = match &sig.output { syn::ReturnType::Type(_, ty) => match &**ty { syn::Type::Path(path) => path, _ => return None, }, _ => return None, }; let last_segment = path.path.segments.last()?; let ident = &last_segment.ident; let res = if ident == "Result" { "Ok" } else if ident == "Option" { "Some" } else { return None; }; Some(res) }