Skip to content

Tracking Issue for once_cell_try #109737

Open

Description

@tgross35

View all comments

This supercedes #74465 after a portion of once_cell was stabilized with #105587

Feature gate: #![feature(once_cell_try)]

This is a tracking issue for the *try* methods that go with the once_cell feature. Initially they were part of stabilization under once_cell (#105587) but was removed due to compatibility concerns with try trait v2 (#105587 (comment)).

Public API

impl<T> OnceCell<T> {
    pub fn get_or_try_init<F, E>(&self, f: F) -> Result<&T, E> where F: FnOnce() -> Result<T, E>;
}

impl<T> OnceLock<T> {
    pub fn get_or_try_init<F, E>(&self, f: F) -> Result<&T, E> where F: FnOnce() -> Result<T, E>;
}

Steps / History

Unresolved Questions

  • None yet.

cc @joboet @matklad just for reference

Footnotes

  1. https://std-dev-guide.rust-lang.org/feature-lifecycle/stabilization.html ↩

Activity

  1. added
    C-tracking-issueCategory: An issue tracking the progress of sth. like the implementation of an RFC
    T-libs-api[DEPRECATED; DO NOT USE]
    on Mar 29, 2023
  2. BurntSushi commented on Mar 30, 2023

    @BurntSushi
    Member

    I think I'd say that the question of whether to make the fallible routines generic over Try is unresolved at this point. See: #107122 (comment) and #84277 (comment)

  3. tgross35 commented on Mar 30, 2023

    @tgross35
    MemberAuthor

    I'm kind of curious of how and how often these functions are used in the ecosystem, does anyone know of any examples? They're not used anywhehre in rustc

  4. BurntSushi commented on Mar 30, 2023

    @BurntSushi
    Member

    I posted a comment on that PR with the results of a quick search that I did: #107122 (comment)

  5. sutes-work commented on Feb 22, 2024

    @sutes-work

    Related to this, I want something like: TryLazyLock. Not sure if this is the right place to comment (sorry if it's not!).

    I looked to see how get_or_try_init is implemented, but unfortunately, the poison method on Once isn't exposed publicly. It would be nice of Once had an API that would support this.

  6. tgross35 commented on Feb 22, 2024

    @tgross35
    MemberAuthor

    @sutes-work can you give an example of what a TryLazyLock API would do differently from get_or_try_init?

  7. sutes-work commented on Feb 22, 2024

    @sutes-work

    So I can do this:

    struct TryLazyLock<T, F> {
        data: OnceLock<T>,   
        init_fn: UnsafeCell<Option<F>>,
    }
    

    and it's not so bad, but it costs me space for the init_fn. I should be able to share that space with T. I should be able to have:

    struct TryLazyLock<T, F> {
        once: Once,
        value: UnsafeCell<State<T, F>>,
    }
    
    enum State {
       PreInit(F),
       Complete(T)
    }
    

    It's a bit rough, but hopefully you get the idea.

  8. tgross35 commented on Feb 22, 2024

    @tgross35
    MemberAuthor

    Is the main goal of the suggestion to save storage space? That is the case already, the actual implementation has

    // We use the state of a Once as discriminant value. Upon creation, the state is
    // "incomplete" and `f` contains the initialization closure. In the first call to
    // `call_once`, `f` is taken and run. If it succeeds, `value` is set and the state
    // is changed to "complete". If it panics, the Once is poisoned, so none of the
    // two fields is initialized.
    union Data<T, F> {
        value: ManuallyDrop<T>,
        f: ManuallyDrop<F>,
    }
    
    pub struct LazyLock<T, F = fn() -> T> {
        once: Once,
        data: UnsafeCell<Data<T, F>>,
    }

    #[unstable(feature = "lazy_cell", issue = "109736")]
    pub struct LazyLock<T, F = fn() -> T> {
    once: Once,
    data: UnsafeCell<Data<T, F>>,
    }

    See #107329

  9. sutes-work commented on Feb 22, 2024

    @sutes-work

    Right, but F is fn() -> T when I want F to be fn() -> Result<T, E>.

  10. tgross35 commented on Feb 22, 2024

    @tgross35
    MemberAuthor

    Oh are you just looking for something to make LazyCell/LazyLock with result functions more ergonomic?

    This works as-is:

    #![feature(lazy_cell)]
    
    use std::sync::LazyLock;
    
    static OK: LazyLock<Result<&'static str, &'static str>> =
        LazyLock::new(|| Ok("success!"));
    static ERROR: LazyLock<Result<&'static str, &'static str>> =
        LazyLock::new(|| Err("error!"));
    
    
    fn main() -> Result<(), String> {
        println!("ok value: {}", (*OK)?);
        println!("error value: {}", (*ERROR)?);
        
        Ok(())
    }

    That prints:

    ok value: success!
    Error: "error!"
    

    There is an ergonomic downside that you can't get an owned error. Feel free to propose an API if you have something in mind, or share code snippets that could be improved.

  11. sutes-work commented on Feb 23, 2024

    @sutes-work

    That's not what I want. I want to call the callback multiple times so it keeps trying. I want:

    // Returns the value if the evaluation has already succeeded. Otherwise,
    // tries to evaluate the lazy value and returns an error if it fails. Multiple
    // calls to try_force will result in the initializer being called multiple times
    // until it succeeds.
    pub fn try_force(this: &LazyLock<T, F>) -> Result<&T, E>;
    

    As you noted, you don't get an owned error, which is problematic for me since the errors I'm dealing with aren't copyable (which I believe is generally the case).

    It's possible that such an API isn't generally useful which is why I hinted that exposing an API on Once that allows me to build such a thing myself would be helpful; I think my own TryOnceLock would be relatively simple: it's Once that seems hard. One option is to expose the poison method, but that feels like a hack to me since in this case it's really the same as the Incomplete state (there was no panic).

  12. tgross35 commented on Feb 23, 2024

    @tgross35
    MemberAuthor

    The usecase in general makes sense, you might be interested to bring it up to the once_cell crate, where a lot of this API was initially developed.

    Having a panicky initializer function sounds like a pretty unfortunate interface. If you really can't use a Result API instead, call_once_force might be what you need. Or use a catch_unwind wrapper around your initializer function to convert those poisons to Results.

    Feel free to drop by on Zulip to discuss this further if you're trying to refine some ideas for new APIs https://rust-lang.zulipchat.com/

  13. sutes-work commented on Feb 23, 2024

    @sutes-work

    you might be interested to bring it up to the once_cell crate,

    I'll have a look into that.

    Having a panicky initializer function sounds like a pretty unfortunate interface.

    I think there might be a misunderstanding: I'm commenting on the way that get_or_try_init seems to be implemented: https://doc.rust-lang.org/src/std/sync/once_lock.rs.html#385. It calls poison there, but poison isn't public so I can't implement my own TryLazyLock using Once.

    For now, I need stable Rust so I've just used the equivalent of:

    struct TryLazyLock<T, F> {
        data: OnceCell<T>,   
        init_fn: UnsafeCell<Option<F>>,
    }
    

    (You can see it here: https://fxrev.dev/995693.)

  14. briansmith commented on Jun 5, 2024

    @briansmith
    Contributor

    The documentation says:

    If the cell was empty and f failed, an error is returned.

    But, it is missing the most important part:

    - If the cell was empty and f failed, an error is returned.
    + If the cell was empty and f failed, an error is returned, and the cell remains uninitialized.

    Importantly, even though the internal Once in OnceLock may be poisoned, there is actually no poisoning in OnceLock, as OnceLock will consider a poisoned internal Once to be uninitialized.

  15. 28 remaining items

  16. added
    T-libsRelevant to the library team, which will review and decide on the PR/issue.
    and removed
    T-libs-api[DEPRECATED; DO NOT USE]
    on Aug 12, 2026
  17. added a commit that references this issue on Sep 3, 2026
  18. added a commit that references this issue on Sep 5, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    C-tracking-issueCategory: An issue tracking the progress of sth. like the implementation of an RFCT-libsRelevant to the library team, which will review and decide on the PR/issue.disposition-mergeThis issue / PR is in PFCP or FCP with a disposition to merge it.proposed-final-comment-periodProposed to merge/close by relevant subteam, see T-<team> label. Will enter FCP once signed off.

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions