Allow detecting if Instant::now is actually supported by the platform

There have been some recent issues in Tokio as metrics are stabilized that start calling Instant::now in general purpose code paths.

The problem is that there isn't actually a way to tell if Instant::now is going to panic or not. I don't think there is currently anyway to work around this other than heuristics.

The two possible options that come to mind:

  • Instant::try_now to handle this at Runtime based on whether or not the platform actually supports it
  • A cfg gate that libraries could use to handle the situation.

Ultimately, probably both are useful for different use cases in the long term, but either would be sufficient.

5 Likes

I believe the library team would generally consider having Instant::now support as a pre-requisite for being an std target (as opposed to no_std).

wasm32-unknown-unknown is an exception for historic reasons but for the above reason I doubt this is something we'd add either lang or libs support for detecting.

3 Likes

The way to detect this is cfg!(all(target_arch = "wasm", target_os = "unknown", target_vendor = "unknown")). No new targets with such basic missing features will be added in the future, so only excluding wasm32-unknown-unknown works fine.

5 Likes

... and arguably we should look into ways to deprecate and eventually remove std from that target as well, given what a pile of hacks it is and how much confusion it causes.

10 Likes

I don't think that's a realiatic path to persue. I see it as lore realistic and better to instead abandon this target long-term and move on to more specialized targets that can or cannot provide std. But that is something more long term.

1 Like

Another path forward could be to further subdivide std (or make it possible to use std with feature flags). In particular, it is very useful to have std::sync exist under the "this platform has exactly one thread" premise. It would be nice if Rust offered that capability to other no_std targets too — any platform where only one thread exists (and Rust is not being used to write signal/interrupt handlers) is a platform where Mutex can be provided as essentially a RefCell (no synchronization, just run time borrow checking). This means that libraries which need interior mutability can be designed to have unconditionally Sync items, and rely on the build context to provide a suitable real-Mutex or stub-Mutex as appropriate for the circumstances, rather than the status quo where libraries have to offer conditionally Sync items or use some kind of swappable mutex implementation (complicating everything with generics or feature flags).

9 Likes

I hope that build-std will make this feasible in the long term, perhaps even unifying core and std by disabling a bunch of features.

3 Likes

IMHO the only good answer is nuking the horrible targets that pretend to support things that they don't.

1 Like

Partial support for std is a more general problem though. For example off the top of my head I know of:

  • ESP-IDF supports most of std as a Unix-like, but not processes (it is a high end microcontroller target, but processes doesn't make any sense in that context).
  • UEFI has a bunch of runtime services for things like files, but as I understand it, there are also some things that don't exist. (Not a target I worked with myself, so I don't know any more specifics).

I wouldn't be surprised if there are other (or in the future will be other) targets like that. And it would be silly to deny access to threads, files and network sockets, just becuse a target doesn't support processes.

There are many embedded OSes that can at least do threads, but maybe not much else outside core/alloc.

I don't know what the clean solution to this would be. Maybe feature flags and build-std? Maybe it would make sense to split std into further crates: threads, fs, net, time and proc perhaps? But it is something to think about, because it something that will need to be solved, eventually.

5 Likes

The unicorn end goal of std-as-a-crate is exactly that — std has some set of denominationally useful cargo feature flags, and each target's std crate either implements the feature or throws a build error if an unsupported feature is requested. The default feature set would be everything provided by all tier 1 targets, and that can imply/slice into however many subfeatures are needed to capture the feature subsets provided by different environments.

3 Likes

Lack of filesystem and lack of processes are much less problematic than lack of Instant::now(), because

  • The APIs are fallible, so they can return errors instead of panicking.
  • What files and processes exist are platform-dependent, and they can have internally consistant empty stubs:
    • The current process is the only process that exists.
    • The only file that exists is an empty, read-only root directory.
    • And you can’t start any child processes because there’s no executables to execute.

There is very little practical difference between “this platform has no file system” and “you don’t know any paths to any files relevant to your application or directories that it would be appropriate to write to”. (I’ll admit that std::env::temp_dir() is a problem for this story, but even a valid temporary directory might be out of disk space.)

It would be good to make these distinctions if possible, but while it’s not possible, stubs are a reasonable approach for filesystem and processes.

8 Likes

Another example where the current core/alloc/std split breaks down is when implementing services for a microkernel OS (e.g. Redox OS):

Most of std is theoretically available for any one service, but you can't use fs in the service implementing the file system, nor can you use time in your time service, etc. But (for example) using files for debug logging in your network service is fine, and using network for debug logging in your file system service also works[1].


  1. Assuming you don't do both at the same time. ↩︎

4 Likes