rust-lang / rust-lang/libs-team

ACP For `OnceLock::get_unchecked` and `LazyLock::get_unchecked`

Open
#654 4 comments 2 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

ACP-accepted api-change-proposal
Dominant language
Rust
Stars
178
Forks
28
Avg merge
15m
Merged PRs (30d)
1

Description

Proposal

Problem statement

Currently there is no way to get a reference to the value in a OnceLock or LazyLock without there being an atomic initialization check on every access, even if you know it has already been initialized, which adds slight overhead to every access.

Motivating examples or use cases

This is one use case where I wanted this:

use derive_more::derive::{Debug, Display};
use lasso::{Spur, ThreadedRodeo};
use rustc_hash::FxBuildHasher;
use std::sync::OnceLock;

#[derive(Debug, Display, Clone, Copy, PartialEq, Eq, Hash)]
#[display("{}", self.as_str())]
#[debug("{:?}", self.as_str())]
pub struct InternedStr(Spur);

impl InternedStr {
    pub fn intern(s: &str) -> Self {
        InternedStr(
            INTERNER
                .get_or_init(|| ThreadedRodeo::with_hasher(FxBuildHasher))
                .get_or_intern(s),
        )
    }

    pub fn as_str(self) -> &'static str {
        // SAFETY: if `self` has been constructed, then `INTERNER` has already been initialized by `Self::intern`
        // even though this uses `unwrap_unchecked`, the compiler still needs to atomically load the
        // initialization flag inside of `get` because the atomic ordering could matter, but it does not here
        let interner = unsafe { INTERNER.get().unwrap_unchecked() };

        // SAFETY: if `self` has been constructed, then `self.0` was retrieved from `INTERNER` so it will be resolved
        unsafe { interner.try_resolve(&self.0).unwrap_unchecked() }
    }
}

static INTERNER: OnceLock<ThreadedRodeo<Spur, FxBuildHasher>> = OnceLock::new();

Solution sketch

The solution would be having OnceLock::get_unchecked/LazyLock::get_unchecked so that the atomic initialization flag doesnt have to be read at all.

They would have these signatures:

impl<T, F> LazyLock<T, F> {
    pub unsafe fn get_unchecked(this: &Self) -> &T;
    pub unsafe fn get_unchecked_mut(this: &mut Self) -> &mut T;
}
impl<T> OnceLock<T> {
    pub unsafe fn get_unchecked(&self) -> &T;
    pub unsafe fn get_unchecked_mut(&mut self) -> &mut T;
}

Alternatives

There are no other alternatives.

Contributor guide

No contributing guide indexed for this repository

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Start by reviewing the proposed OnceLock::get_unchecked and LazyLock::get_unchecked APIs and the motivating InternedStr example. Assess the safety contracts and whether the proposed immutable and mutable methods meet the stated goal; done means reaching and documenting an ACP decision on the API design.

Written by the indexing model from the issue text.

Assessment

Tech stack
rust
Domain
backend-api-design
Issue type
Feature
Difficulty
5/5
Estimated time
Over a week
Activity status
Stale
Clarity
Clearly specified
Newbie friendliness
25/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.