[lang-team-minutes] integer conversion and portability writ large


#1

We discussed https://github.com/rust-lang/rfcs/issues/1748 in the lang team meeting today. The issue here is that it would be nice to make some “common sense” assumptions about usize – e.g., that it can be overapproximated with u64. Of course, this could hinder portability in the future (to 128-bit platforms, if those will ever exist). More generally – and probably more realistically – if one is specifically targetting 16-, 32-, or 64-bit, one might want to be able to interconvert usize with those types. So what should we do?

Some while back, @aturon started this thread: “A vision for platform architecture / configuration-specific APIs”. This thread didn’t really go anywhere but it suggests a way to handle this scenario. The basic idea is that the libraries and compiler are oriented give you portability “by default” amongst “major” platforms (which we’ll have to define, but I think mac/linux/windows, 32/64-bit is a likely set). If you try to use things that won’t ensure that portability – such as a windows-specific API – you would get lints warning you against that. You can choose to “allow” these lints if that doesn’t bother you.

The interesting part about this is that we can also have lints for portability hazards for lesser-known or more specialized platforms, but those lints default to allow. This means that you can toggle those lints to “deny” if you want to ensure compatibility with those platforms (but, e.g., you may want to re-allow those lints in some parts of your codebase, such as your test code). This basically means that the lints steer you to compatibility with major platforms by default, and other platforms if you choose.

It seems like this approach could well be applied to interconversion between usize and u64 (or other numeric types). It’s not clear whether we would want to use the From conversion traits to do said interconversion, though it’s not inconceivable (but we’d have to extend the lints to be able to fire when a particular impl is used).

I will also point out that similar requirements keep coming up in other cases too (e.g., just this week, I brought it up when discussing the composability of unsafe code, but it also applies to embedded use cases). It seems like so long as we want to support a wide, diverse set of platforms and use-cases, there is going to be some amount of divergence where some people want to specialize for their target, but most people want to retain portability amongst “major” systems, without being hassled with supporting every last case.

Thoughts?


#2

I’m curious, does rustc have tools to map a trait method call a.method() into a concrete trait impl method fn method() { /* implementation */ } in non-generic and non-dynamic context (type of a is known and is not a trait) before monomorphization? I remember I needed something like this last year or so, but haven’t found anything.


#3

Monomorphization is not magical, just another user of rustc APIs. For a lint example, see “unconditional recursion”.


#4

Well, yes but it could do something one-time and expensive, for example. So the task would be similar to something like “resolve something before the main resolution pass”. Thanks for the example, I’ll look how it works.


#5

One thing I was thinking about as I wrote this up was that we would probably want to address the “transitive dependency” case. That is, even today, if I use some library, and it uses Windows APIs, I have an indirect dependency on Windows APIs that I am not aware of. If I am concerned about this, it’d be nice if I could assert otherwise. I’m not quite sure what this looks like – perhaps something like cap-lints, but the opposite (forcing the lints to become an error downstream)?


#6

Why not leverage cargo here?

I would propose that when cargo is used, it by default assumes portability, and a section/attribute is otherwise available to list the platforms that the crate supports.

Besides documenting those assumptions outside the Rust code, in an easily accessible format, it also paves the way for crates.io to document the portability of each crate (which matters for searching said crates).


On an other note, how would this play with a portable library built on top of non-portable ones?

For example, the I/O layer differs between unix and windows, so the crate depends on both unix and windows libraries… conditionally.

The set of dependencies/target APIs should thus depend on the feature flags, I suppose.


#7

Since the scenarios post has reminded me, I’ll reply breifly here: it’s tricky to be sure whether you’re actually depending on Windows apis until you recompile for a different platform (‘newtarget’). Possible results are

  1. there were no cfg blocks anywhere, compilation fails (could have been detected as a hard dependency)
  2. there were cfg blocks, but none for newtarget (not a hard dependency on windows, could have been detected if you listed newtarget as something you explicitly want to support and ran a few compiler passes while pretending to be newtarget)
  3. there were cfg blocks for newtarget, but you’re the first one to compile for it and you discover lifetime/variable naming/unstable feature errors (yes it happens) (same as above)
  4. there were cfg blocks for newtarget, but there are symbol errors at link time (not a hard dependency on windows, can only be detected by doing the full compilation for the platform)
  5. it works

If your lint just checks for purely portable things it’ll only support point 1, which isn’t great.


#8

The status quo is that everybody makes assumptions about the size of usize and just hides it away with as conversions that provide no type safety. So any amount of documentation and checking will be an improvement.

The ideal is that you declare in your cargo.toml or crate root which range of widths you want to support and automatically get the appropriate From impls, with static checking, including dependent crates. OS specific stuff is a seperate issue - I don’t think there’s any reasonable way to provide a non platform specific interface on top of platform specific integer code - either you’re assuming you can do the conversion or not.

Also, once this is done, as conversions involving isize/usize should be turned into lint warnings.