Static lifecycle promotion of temporary references

[Rust official forum post sorting] series: https://zjp-cn.github.io/rust-note/forum/static-promotion.html

static promotion

Static lifecycle promotion of temporary references

stay RFC 1414: rvalue static promotion The static life cycle promotion function of the right value is implemented in, that is, the
Constant expression The right value of is promoted to static memory instead of being placed in stack slots, and created directly
'static reference to expose these values. A typical example is to support code similar to the following:

let x: &'static u32 = &42;
fn return_static_ref() -> &'static u32 { &42 }

// u32 can be replaced with any data structure
struct Custom(u32);
let x: &'static Custom = &Custom(42);
fn return_static_ref_custom() -> &'static Custom { &Custom(42) }

use std::cell::Cell;
type Enum = Option<Cell<u32>>;
let x: &'static Enum = &None;
fn return_static__ref_none() -> &'static Enum { &None }

In other words, for let x: &u32 = &42; Data 42 is placed on the stack, and let x: &'static U32 = &42; Then put the data 42
It is placed in static memory, so obviously the life cycle of the latter can be longer than that of the former, which is particularly useful for functions that return &'static t.

In addition, RFC also summarizes a rule:

// Click the button in the upper right corner to run this code normally

use std::mem::size_of;
type T = usize;
const CONST_EXPR: &'static usize = &size_of::<usize>();

// If this line compiles well
const X: &'static T = &CONST_EXPR;

// Then this line should also be compiled
let x: &'static T = &CONST_EXPR;

Not all references are eligible for promotion

First, the most important premise is that this function is only for the value of constant expressions: common sources of constant expressions are literals, constants, statics, const fn.

Secondly, a special case is mentioned in the RFC:

If a constant expression contains a construction expression of unsafe cell {...}, the temporary reference cannot be promoted to a static lifecycle reference.

use std::cell::{Cell, UnsafeCell};

// error[E0515]: cannot return reference to temporary value
fn not_allowed() -> &'static Option<UnsafeCell<u32>> { &Some(UnsafeCell::new(42)) }

// Unsafe cell is used behind Cell::new(...), so it cannot be promoted
fn not_allowed_too() -> &'static Option<Cell<u32>> { &Some(Cell::new(42)) }

This is to ensure that the promoted value is truly immutable after reference.

panic in constant

the other one RFC 2345: const panic Stipulated
panic! It can be used in constants, const fn and other occasions (various macros assert* based on it can also be used in the same occasions), and
The result is a compile time error.

So, if you encounter panic! Constant expression of, temporary reference cannot be promoted to'static:

const fn size_of<T>() -> usize { panic!() }

// Compilation failure specified in RFC 2345
// error[E0080]: evaluation of constant value failed
const X: &'static usize = &size_of::<()>();

// Compilation failed, unable to promote to'static
// error[E0716]: temporary value dropped while borrowed
let x: &'static usize = &size_of::<()>();

Note that static promotion refers to the problem of reference (to be exact, shared reference &t) promoting the life cycle:

const fn size_of<T>() -> usize { panic!() }

// Compilation failure specified in RFC 2345
// error[E0080]: evaluation of constant value failed
const X: usize = size_of::<()>();

// The following code has nothing to do with static promotion
// The compilation passed, but the operation failed
let x: usize = size_of::<()>();
let x: &usize = &size_of::<()>();

Qualified references are not necessarily promoted automatically

This part does not seem to be mentioned in the RFC.

Start with a simple example.

use std::mem::size_of;

// The following two lines of code conform to the rules mentioned in the first part
const _: &'static usize = &size_of::<usize>();
let _: &'static usize = &size_of::<usize>();

However, consider the following (hereinafter referred to as "static promotion" for short):

// This code block can be edited directly

fn main() {
    // Code OK
    const _: &'static usize = &size_of::<usize>();

    // The following code * * does not conform to the rules mentioned in * * part I
    // error[E0716]: temporary value dropped while borrowed
    let _: &'static usize = &size_of::<usize>();
}

// For std::mem::size_of, but cannot be "statically promoted"
const fn size_of<T>() -> usize { std::mem::size_of::<T>() }
// Even simple literal cannot be "statically promoted"
// const fn size_of<T>() -> usize { 0 }

Review the rules mentioned in part I RFC:

// If this line compiles well
const X: &'static T = &CONST_EXPR;

// Then this line should also be compiled
let x: &'static T = &CONST_EXPR;

const _: &' static usize = &size_ of::<usize>(); This line describes &size_ Of:: <use> () is qualified for "static promotion", and the compiler
let _: &' static usize = &size_of::<usize>(); Failed to put the constant function size_of
The return value of is regarded as a constant, but as the value of an ordinary function, so this value is placed on the stack, so it is "temporary" rather than "static".

There are some ways to solve this problem. The basic logic is to explicitly tell the compiler that the return value of a constant function is a constant.

be careful:

  • let _: &' Static t is similar to FN f() - > &'static t in static promotion, which promotes temporary references to static references.
  • The link example is based on a vtable construction skills post
    This article is also sorted out by this post and its discussion.

Method 1: constant items

// This code block can be edited directly

fn main() {
    const X: usize = size_of::<usize>();
    // Of course, you can also:
    // const X: &'static usize = &size_of::<usize>();
    let _: &'static usize = &X;
}

const fn size_of<T>() -> usize { std::mem::size_of::<T>() }
// const fn size_of<T>() -> usize { 0 }

This is actually the practice before the implementation of static enhancement function, so this is the most basic method.

The disadvantage is that if you encounter a function with type parameters such as FN f<t> () - > &'static... constants cannot be used inside the function(
vtable example ).

// This code block can be edited directly

fn f<T>() -> &'static usize {
    // error[E0401]: can't use generic parameters from outer function
    const X: usize = size_of::<T>();
    &X
}

const fn size_of<T>() -> usize { std::mem::size_of::<T>() }
// const fn size_of<T>() -> usize { 0 }
fn main() { }

Method 2: associated constants

There are two types of definitions of association constants: impl Type and impl trail for type.

// This code block can be edited directly

fn f<T>() -> &'static usize {
    struct Type<T>(*mut Self);
    impl<T> Type<T> {
        const X: usize = size_of::<T>();
    }
    &Type::<T>::X
}

const fn size_of<T>() -> usize { std::mem::size_of::<T>() }
// const fn size_of<T>() -> usize { 0 }
fn main() { }
// This code block can be edited directly

fn f<T>() -> &'static usize {
    trait Trait<T> {
        const X: usize = size_of::<T>();
    }
    struct Type;
    impl<T> Trait<T> for Type {}
    &<Type as Trait<T>>::X
}

const fn size_of<T>() -> usize { std::mem::size_of::<T>() }
// const fn size_of<T>() -> usize { 0 }
fn main() {}

This solves the problem that constants in method 1 cannot use type parameters. (vtable example type-assoc-const and trait-assoc-const)

Method 3: const block

// This code block can be edited directly and requires nightly rustc

#![feature(inline_const)]

fn f<T>() -> &'static usize { const { &size_of::<T>() } }

const fn size_of<T>() -> usize { std::mem::size_of::<T>() }
// const fn size_of<T>() -> usize { 0 }
fn main() {}

This is the simplest way. See details RFC 2920. (vtable example)

Tags: Rust programming language

Posted by dmikester1 on Sun, 24 Jul 2022 04:51:14 +0930