Allow function lifetime elision and explicit `for<'a>` annotations on closures.

Overview

::higher-order-closure

Allow function lifetime elision and explicit for<'a> annotations on closures.

Repository Latest version Documentation MSRV unsafe forbidden License CI

Motivation / Rationale

See the RFC #3216: this crate is a Proof-of-Concept of the ideas laid out therein1.

Click to expand

The following example fails to compile:

let f = |x| {
    let _: &i32 = x;
    x
};
{
    let scoped = 42;
    f(&scoped);
} // <- scoped dropped here.
f(&42);
error[E0597]: `scoped` does not live long enough
  --> src/lib.rs:10:7
   |
10 |     f(&scoped);
   |       ^^^^^^^ borrowed value does not live long enough
11 | } // <- scoped dropped here.
   | - `scoped` dropped here while still borrowed
12 | f(&42);
   | - borrow later used here

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

Indeed, the signature of f in that example is that of:

impl Fn(&'inferred i32) -> &'inferred i32

wherein 'inferred represents some not yet known (to be inferred) but fixed lifetime.

Then,

{
    let scoped = 42;
    f(&scoped); // `'inferred` must "fit" into this borrow…
} // <- and thus can't span beyond this point.
f(&42) // And yet `'inferred` is used here as well => Error!

The solution, then, is to explicitly annotate the types involved in the closure signature, and more importantly, the lifetime "holes" / placeholders / parameters involved in that signature:

           // Rust sees this "hole" early enough in its compiler pass
           //                       to figure out that the closure signature
           // vv                    needs to be higher-order, **input-wise**
let f = |_x: &'_ i32| {
};
{
    let scoped = 42;
    f(&scoped);
}
f(&42);

This makes it so the input-side of the closure signature effectively gets to be higher-order. Instead of:

impl Fn(&'inferred_and_thus_fixed i32)...

for some outer inferred (and thus, fixed) lifetime 'inferred_and_thus_fixed, we now have:

impl for<'any> Fn(&'any i32)...

This works, but quid of returning borrows? (all while remaining higher-order)

Indeed, the following fails to compile:

let f = |x: &'_ i32| -> &'_ i32 {
    x // <- Error, does not live long enough.
};
error: lifetime may not live long enough
 --> src/lib.rs:5:5
  |
4 | let f = |x: &'_ i32| -> &'_ i32 {
  |             -           - let's call the lifetime of this reference `'2`
  |             |
  |             let's call the lifetime of this reference `'1`
5 |     x // <- Error, does not live long enough.
  |     ^ returning this value requires that `'1` must outlive `'2`

The reason for this is that "explicit lifetime 'holes' / placeholders become higher-order lifetime parameters in the closure signature" mechanism only works for the input-side of the signature.

The return side keeps using an inferred lifetime:

let f = /* for<'any> */ |x: &'any i32| -> &'inferred i32 {
    x // <- Error, does not live long enough (when `'any < 'inferred`)
};

we'd like for f there to have the fn(&'any i32) -> &'any i32 signature that functions get from the lifetime elision rules for function signatures.

Hence the reason for using this crate.

Examples

This crate provides a higher_order_closure! macro, with which one can properly annotate the closure signatures so that they become higher-order, featuring universally quantified ("forall" quantification, for<…> in Rust) lifetimes:

#[macro_use]
extern crate higher_order_closure;

fn main ()
{
    let f = higher_order_closure! {
        for<'any> |x: &'any i32| -> &'any i32 {
            x
        }
    };
    {
        let local = 42;
        f(&local);
    }
    f(&42);
}

The lifetime elision rules of function signatures apply, which means the previous signature can even be simplified down to:

#[macro_use]
extern crate higher_order_closure;

fn main ()
{
    let f =
        higher_order_closure! {
            for<> |x: &'_ i32| -> &'_ i32 {
                x
            }
        }
    ;
}

or even:

#[macro_use]
extern crate higher_order_closure;

fn main ()
{
    let f =
        higher_order_closure! {
            |x: &'_ i32| -> &'_ i32 {
                x
            }
        }
    ;
}
  • Because of these lifetime elision in function signatures semantics, it is highly advisable that the elided_lifetimes_in_paths be, at the very least, on warn when using this macro:

    //! At the root of the `src/{lib,main}.rs`.
    #![warn(elided_lifetimes_in_paths)]

Extra features

Macro shorthand

The main macro is re-exported as a hrtb! shorthand, to allow inlining closures with less rightward drift:

#[macro_use]
extern crate higher_order_closure;

fn main ()
{
    let f = {
        let y = 42;
        hrtb!(for<'any> move |x: &'any i32| -> &'any i32 {
            println!("{y}");
            x
        })
    };
}

Outer generic parameters

Given how the macro internally works2, generic parameters "in scope" won't, by default, be available in the closure signature (similar to consts and nested function or type definitions).

In order to make them available, higher_order_signature! accepts an initial optional #![with<simple generics…>] parameter (or even #![with<simple generics…> where clauses…] if the "simple shape" restrictions for the generic parameters are too restrictive).

"simple shaped" generics macro restrictions

The generics parameters inside #![with<…>] have to be of the form:

<
    'a, 'b : 'a, ...
    T, U : ?Sized + 'a + ::core::fmt::Debug, V, ...
>

Mainly:

  • at most one super-lifetime bound on each lifetime,

  • the super-bounds on the types must be exactly of the form:

    1. optional ?Sized,
    2. followed by an optional lifetime bound,
    3. followed by an optional trait bound.
    4. And nothing more. If you need more versatility, use the where clauses.

In practice, however, the bounds are seldom needed, since such generics are only used for the signature of the closure, not its body / implementation.

#[macro_use]
extern crate higher_order_closure;

use ::core::fmt::Display;

fn example<T : Display> (iter: impl IntoIterator<Item = (bool, T)>)
{
    let mb_display = higher_order_closure! {
        #![with<T>]
        |hide: bool, elem: &'_ T| -> &'_ dyn Display {
            if hide {
                &"<hidden>"
            } else {
                elem
            }
        }
    };
    for (hide, elem) in iter {
        println!("{}", mb_display(hide, &elem));
    }
}

fn main ()
{
    example([(false, 42), (true, 27), (false, 0)]);
}

Mixing higher-order lifetime parameters with inferred ones

Since inside higher_order_closure!, '_ has the semantics of lifetime elision for function signatures, it means that, by default, all the lifetime parameters appearing in such closure signatures will necessarily be higher-order.

In some more contrived or rare scenarios, this may be undesirable.

In that case, a nice way to work around that is by artificially introducing generic lifetime parameters as seen above, and using these newly introduced named lifetime parameters where the inferred lifetime would be desired.

#[macro_use]
extern crate higher_order_closure;

fn main ()
{
    let y = 42;
    let f = higher_order_closure! {
        #![with<'inferred>]
        for<'any> |x: &'any i32| -> (&'any i32, &'inferred i32) {
            (x, &y)
        }
    };
    let (&a, &b) = f(&27);
    assert_eq!(a + b, 42 + 27);
}

Footnotes

  1. with the exception of allowing elided lifetimes to have a meaning (chosen higher-order), which the RFC cannot do in order to be future-proof.

  2. it generates a "closure identity" helper function, with the desired higher-order signatures embedded as Fn bounds on its parameters, thus making it act as a "funnel" that only lets closure with the right signature pass through).

You might also like...
Fast and simple datetime, date, time and duration parsing for rust.

speedate Fast and simple datetime, date, time and duration parsing for rust. speedate is a lax† RFC 3339 date and time parser, in other words, it pars

In this repository you can find modules with code and comments that explain rust syntax and all about Rust lang.
In this repository you can find modules with code and comments that explain rust syntax and all about Rust lang.

Learn Rust What is this? In this repository you can find modules with code and comments that explain rust syntax and all about Rust lang. This is usef

A tool and library to losslessly join multiple .mp4 files shot with same camera and settings

mp4-merge A tool and library to losslessly join multiple .mp4 files shot with same camera and settings. This is useful to merge multiple files that ar

A tray application for Windows that gives you push notifications and instant downloads of new posts, messages and stories posted by models you subscribe to on Onlyfans.

OF-notifier A tray application for Windows that gives you push notifications and instant downloads of new posts, messages and stories posted by models

A simpler and 5x faster alternative to HashMap in Rust, which doesn't use hashing and doesn't use heap

At least 5x faster alternative of HashMap, for very small maps. It is also faster than FxHashMap, hashbrown, ArrayMap, and nohash-hasher. The smaller

A comprehensive and FREE Online Rust hacking tutorial utilizing the x64, ARM64 and ARM32 architectures going step-by-step into the world of reverse engineering Rust from scratch.
A comprehensive and FREE Online Rust hacking tutorial utilizing the x64, ARM64 and ARM32 architectures going step-by-step into the world of reverse engineering Rust from scratch.

FREE Reverse Engineering Self-Study Course HERE Hacking Rust A comprehensive and FREE Online Rust hacking tutorial utilizing the x64, ARM64 and ARM32

Leetcode Solutions in Rust, Advent of Code Solutions in Rust and more

RUST GYM Rust Solutions Leetcode Solutions in Rust AdventOfCode Solutions in Rust This project demostrates how to create Data Structures and to implem

:crab: Small exercises to get you used to reading and writing Rust code!
:crab: Small exercises to get you used to reading and writing Rust code!

rustlings 🦀 ❤️ Greetings and welcome to rustlings. This project contains small exercises to get you used to reading and writing Rust code. This inclu

A catalogue of Rust design patterns, anti-patterns and idioms

Rust Design Patterns An open source book about design patterns and idioms in the Rust programming language that you can read here. Contributing You ar

Releases(v0.0.5)
Owner
Daniel Henry-Mantilla
🦀 Feeling crabulous 🦀 https://danielhenrymantilla.github.io
Daniel Henry-Mantilla
Tool to convert variable and function names in C/C++ source code to snake_case

FixNameCase Tool to convert variable and function names in C/C++ source code to snake_case. Hidden files and files listed in .gitignore are untouched.

AgriConnect 4 May 25, 2023
A Quest to Find a Highly Compressed Emoji :shortcode: Lookup Function

Highly Compressed Emoji Shortcode Mapping An experiment to try and find a highly compressed representation of the entire unicode shortcodes-to-emoji m

Daniel Prilik 13 Nov 16, 2021
a function programming language for real world applications made in rust

a function programming language for real world applications made in rust

Tanay Pingalkar 6 Jun 12, 2022
A Rust attribute macro that adds memoization to a function (rhymes with Mickey)

michie (sounds like Mickey) — an attribute macro that adds memoization to a function. Table of contents Features Non-features key_expr key_type store_

Mobus Operandi 16 Dec 20, 2022
A procedural macro to generate a new function implementation for your struct.

Impl New ?? A procedural macro to generate a new function implementation for your struct. ?? Add to your project Add this to your Cargo.toml: [depende

Mohammed Alotaibi 4 Sep 8, 2023
An API for getting questions from http://either.io implemented fully in Rust, using reqwest and some regex magic. Provides asynchronous and blocking clients respectively.

eithers_rust An API for getting questions from http://either.io implemented fully in Rust, using reqwest and some regex magic. Provides asynchronous a

null 2 Oct 24, 2021
Safe, efficient, and ergonomic bindings to Wolfram LibraryLink and the Wolfram Language

wolfram-library-link Bindings to the Wolfram LibraryLink interface, making it possible to call Rust code from the Wolfram Language. This library is us

Wolfram Research, Inc. 28 Dec 6, 2022
This blog provides detailed status updates and useful information about Theseus OS and its development

The Theseus OS Blog This blog provides detailed status updates and useful information about Theseus OS and its development. Attribution This blog was

Theseus OS 1 Apr 14, 2022
Omeglib, a portmanteau of "omegle" and "library", is a crate for interacting with omegle, simply and asynchronously

Omeglib, a portmanteau of "omegle" and "library", is a crate for interacting with omegle, simply and asynchronously. It is intended to suit one's every requirement regarding chat on omegle.

null 1 May 25, 2022