Join GitHub today
GitHub is home to over 31 million developers working together to host and review code, manage projects, and build software together.
Sign upRFC: A portability lint #1868
Conversation
This comment has been minimized.
This comment has been minimized.
|
cc @rust-lang/libs: this is what the "scenarios" idea evolved into. |
aturon
added
T-libs
T-lang
labels
Jan 23, 2017
sfackler
reviewed
Jan 23, 2017
| If you attempted to call `as_raw_fd`, when compiling on Unix you'd get a warning | ||
| from the portability lint that you're calling an API not available on all | ||
| mainstream platforms. There are basically three ways to react (all of which will | ||
| make the warning go away): |
This comment has been minimized.
This comment has been minimized.
sfackler
Jan 23, 2017
Member
Probably worth making it clear that there's a fourth option of explicitly suppressing the lint in cases where the other three options won't work for whatever reason.
This comment has been minimized.
This comment has been minimized.
|
I find this much nicer than scenarios; great job! |
This comment has been minimized.
This comment has been minimized.
|
For me it's super important that |
sfackler
reviewed
Jan 23, 2017
| #[cfg(any(windows, unix))] | ||
| fn portable() { | ||
| // the expression here has portability `any(windows, unix)` | ||
| match_cfg! { |
This comment has been minimized.
This comment has been minimized.
sfackler
Jan 23, 2017
Member
Is this smarter than a normal macro_rules! macro that expands to #[cfg(windows)] { windows_only() } #[cfg(unix)] { unix_only() } would be?
This comment has been minimized.
This comment has been minimized.
aturon
Jan 23, 2017
Author
Member
Yes -- using cfg directly like that would not give you the any(windows, unix) semantics.
This comment has been minimized.
This comment has been minimized.
Ericson2314
Jan 25, 2017
Contributor
Even if we could macro it some other way, match_cfg as a special primitive will make things faster by allowing formulae to be filtered prior to SAT-solving in the exhaustive match case.
sfackler
reviewed
Jan 23, 2017
|
|
||
| The `std` portability will include several implications, e.g.: | ||
|
|
||
| - `std` implies `any(windows, macos, linux)` |
This comment has been minimized.
This comment has been minimized.
sfackler
Jan 23, 2017
Member
Seems like we should be able to cover BSDs in a reasonable way in the base portability level, right?
This comment has been minimized.
This comment has been minimized.
luser
Jan 31, 2017
99% of the platform-specific code I've wound up writing in Rust is simply cfg(unix) vs. cfg(windows), honestly. I know that for very low-level things you can wind up having to write code that's not POSIX, but I have a hard time thinking of an example that would crop up in the standard library.
sfackler
reviewed
Jan 23, 2017
| evidence in either direction. But it is yet another place where the fact that | ||
| it's a lint could help: we may be able to simply skip checking pathological | ||
| cases, if they indeed arise in practice. In any case, it's hard to know how | ||
| concerned to be until we try it. |
This comment has been minimized.
This comment has been minimized.
sfackler
Jan 23, 2017
Member
My impression is that modern solvers are good enough that if you run into an exponential case, you're in "doctor, it hurts when I do this" territory, particularly in a context as constrained as this one. People are not going to be feeding randomized cfgs containing hundreds of clauses into rustc (hopefully)!
This comment has been minimized.
This comment has been minimized.
Ericson2314
Jan 25, 2017
•
Contributor
It's exponential in the number of variables, not the size of the formula (consider any formula can put into a normal form equivalent to a truth table, whose size is 2^num-vars). Since all the variables are defined up-front (no features yet), so the number of crates/items doesn't impact the exponential, I am cautiously optimistic.
sfackler
reviewed
Jan 23, 2017
| struct MyType; | ||
| impl Foo for MyType { | ||
| #[cfg(unix)] |
This comment has been minimized.
This comment has been minimized.
sfackler
Jan 23, 2017
Member
I think you'd probably want this on the overall impl, right? It seems like it'd be relatively easy to see that the impl is missing definitions in some cfgs here.
This comment has been minimized.
This comment has been minimized.
petrochenkov
Jan 23, 2017
•
Contributor
I think if the annotations are restricted to impls themselves (rather than to impl items), then we can catch the lint violation here:
use_foo::<MyType>(); // `MyType: Foo` requires `unix`
^^^^^^
with good enough precision.
This comment has been minimized.
This comment has been minimized.
eddyb
Jan 23, 2017
Member
@aturon @nikomatsakis I think we can even encode this in the trait system, as where cfg("unix") (a custom kind of "predicate" that would get automatically propagated when impls are selected).
The lint could be emitted from the function type-inference/checking context, which tracks obligations (predicates that have to be satisfied) well enough to allow blaming the original source.
This comment has been minimized.
This comment has been minimized.
nikomatsakis
Jan 23, 2017
Contributor
This is not the way I would think about it, but I agree it is probably something we can handle. I think it's more akin to a lifetime constraint. The idea would be to disallow these configuration constraints from affecting how inference works, but propagate them back as a kind of "side constraint" that the caller may wish to enforce. (This fits in with my newer prototypes for how the trait system should work.)
This comment has been minimized.
This comment has been minimized.
nikomatsakis
Jan 23, 2017
Contributor
(We are quite likely just saying the same thing here, though, in our own ways.)
This comment has been minimized.
This comment has been minimized.
eddyb
Jan 23, 2017
Member
Oh, I was unclear I meant the where cfg(x) as "was kept because cfg(x) holds", not doing the actual gating on the fly, but remembering it was done. Though I should probably read the whole RFC before commenting, shouldn't I?
petrochenkov
reviewed
Jan 23, 2017
| As with many lints, the portability lint is *best effort*: it is not required to | ||
| provide airtight guarantees about portability. In particular, the proposed | ||
| definition in this RFC ignores trait implementations and dispatch. In practice, | ||
| this hole shouldn't be too significant: it's pretty rare to implement a trait |
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
jethrogb
reviewed
Jan 23, 2017
| standard primitives. For example `std::os::unix::io::AsRawFd` is a trait with | ||
| the `as_raw_fd` method (to extract a file descriptor). If you were to ignore | ||
| Windows, however, one might expect this API instead to live as a method | ||
| directly on types like `File`, `TcpStream`, etc. Forcing code to live in |
This comment has been minimized.
This comment has been minimized.
jethrogb
Jan 23, 2017
•
Contributor
I don't like the running example of as_raw_fd needing to be an inherent method. If you're on unix (which you are since you're calling as_raw_fd), it makes perfect sense for all file descriptor wrapper types to implement a common trait. For example, if you're writing a crate that implements select/poll, you might want to be generic over AsRawFd.
Same story applies to Windows/HANDLEs/WaitForMultipleObjects
jethrogb
reviewed
Jan 23, 2017
| provide airtight guarantees about portability. In particular, the proposed | ||
| definition in this RFC ignores trait implementations and dispatch. In practice, | ||
| this hole shouldn't be too significant: it's pretty rare to implement a trait | ||
| conditionally based on platform information, and the goal of this RFC is to |
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
sfackler
Jan 23, 2017
Member
That approach would presumably fall out of "style" if this was was implemented.
petrochenkov
reviewed
Jan 23, 2017
| **What does it mean for a portability to be narrower?** In general, portability | ||
| is a logical expression, using the operators `all`, `any`, `not` on top of | ||
| primitive expressions like `unix`. Portability `P` is narrower than portability | ||
| `Q` if `P` *implies* `Q` as a logic formula. |
This comment has been minimized.
This comment has been minimized.
petrochenkov
Jan 23, 2017
Contributor
If attribute values are extended to support basic arithmetic operations in the future (to support version requirements like #[cfg(_MSC_VER >= 1600)] and generally catch up with configuration abilities of C preprocessor), will it create additional problems for this scheme?
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
eddyb
Jan 25, 2017
Member
IMO the problem formulation for SAT is simple enough that you can use heuristics with brute-force worst-case, but for SMT you need a proper solver if you want to get anywhere.
nagisa
reviewed
Jan 23, 2017
|
I still need time to think the idea through, mostly thinking about corner cases. I’d like more examples and samples. I found missing from the RFC details such as where would the implications be stored. RFC says the cargo features get ignored, but does not say what happens with user specified |
| but it's increasingly holding us back from enhancements we'd like to make, and | ||
| even for the needs it covers, it's suboptimal in a few ways. | ||
|
|
||
| **Problems with `std::os`**: |
This comment has been minimized.
This comment has been minimized.
| definition in this RFC ignores trait implementations and dispatch. In practice, | ||
| this hole shouldn't be too significant: it's pretty rare to implement a trait | ||
| conditionally based on platform information, and the goal of this RFC is to | ||
| *guide* code toward mainstream compatibility without imposing a heavy burden. |
This comment has been minimized.
This comment has been minimized.
nagisa
Jan 23, 2017
Contributor
This sounds like a serious problem to me and I feel like the wording here is downplaying the seriousness of this suggestion.
This comment has been minimized.
This comment has been minimized.
Kimundi
Jan 24, 2017
Member
Agreed - I feel like we should at least be able to set the lint to error to get hard gurantees, which means not having holes like that in it.
| definitions and checks that the item's portability is *narrower* than the | ||
| portability of items it references or invokes. For example, `bar` in the above | ||
| could invoke an item with portability `unix` and/or `target_pointer_width = | ||
| "32"`, but not one with portability `linux`. |
This comment has been minimized.
This comment has been minimized.
| // portability `all(any(windows, unix), windows)` | ||
| windows_only() | ||
| } | ||
| unix => { |
This comment has been minimized.
This comment has been minimized.
nagisa
Jan 23, 2017
Contributor
It would be reassuring to see a match_cfg example with more complex CFGs. So far no examples of match_cfg in conjunction with value-cfg (e.g. target_os = "linux") have been provided.
|
|
||
| The `std` portability will include several implications, e.g.: | ||
|
|
||
| - `std` implies `any(windows, macos, linux)` |
This comment has been minimized.
This comment has been minimized.
nagisa
Jan 23, 2017
Contributor
Noting again, that linux and macos are not cfg that get set by compiler itself.
This comment has been minimized.
This comment has been minimized.
nagisa
Jan 23, 2017
Contributor
It seems to me like this implication would make any crate with target_os ≠ any("windows", "macos", "linux") fire the lint on any crate whenever used in code specific to non-currently-tier-1 platform.
For example this code:
#[cfg(target_os = "freebsd")]
fn new_vec() -> Vec<u8> { Vec::new() }
So the body here is windows | macos | linux but the function itself is target_os = "freebsd". Since target_os = "freebsd" is not implied by any of the windows, macos or linux, Vec::new() is considered to be incompatible and gets linted. Am I right? If so, that sounds like a serious issue.
The way I see it, std portability should be derived from every supported target. Looking at platform support page, there’s a number of targets supported, I’ll pick a few:
x86_64-unknown-linux-gnu=all(target_arch = "x86", target_os= "linux", target_vendor = "unknown", target_abi = "gnu", target_pointer_width = "64", ...),mips64-unknown-linux-gnuabi64=all(target_arch = "mips64", target_os = "linux", target_vendor = "unknown", target_abi = "gnuabi64", target_pointer_width = "64", ...),thumbv7m-none-eabi(std not supported) =not(all(target_arch = "...", target_os = "none", ...)),- ...
and if any of those conditions above hold, then the std portability is implied.
Now the question how do you specify it? #![cfg(...)] of all these on src/libstd/lib.rs makes no sense as there’s also a number of custom targets (e.g. x86_64-unknown-linux-{arbitrarylibcabi}) on which libstd runs fine… probably… and wouldn’t anymore.
Eh, I wrote this whole comment and only remembered about non-normative nature of this section.
|
|
||
| Another aspect of portability comparison is the relationship between things like | ||
| `unix` and `linux`. In logical terms, we want to say that `linux` implies | ||
| `unix`, for example. |
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
nagisa
Jan 23, 2017
Contributor
That being said, would be nice to see some expansion in this section wrt value-cfg (e.g. target_os = "linux")
This comment has been minimized.
This comment has been minimized.
Ericson2314
Jan 25, 2017
Contributor
key-value stuff should be transformable to black box constants with a disjointness axiom? At least in the target_os case we don't support arbitrary strings anyways which helps make that feasible.
This comment has been minimized.
This comment has been minimized.
nagisa
Jan 25, 2017
Contributor
@Ericson2314 you can write a custom target which would set a user-specified target_os string (there can’t be multiple possible values though, so your point stands). While I do not think it would be a problem to support key = value cfgs, I would like to see expansion just for completeness sake.
| # Alternatives | ||
| [alternatives]: #alternatives | ||
|
|
||
| The main alternatives are: |
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
|
I like the overall feeling of this RFC more than the scenarios proposal. I do have some lingering questions and remarks. It feels like we're trying to prove transitive properties of code. But we're using a lint instead of the type system. Wouldn't this make more sense to be encoded directly into types? What is the portability of
Lints are not enabled for dependent crates. How will I know if my dependencies meet the platform I'm targeting? There's nothing in this RFC about solving the cfg+rustdoc problem. That's probably intentional, but I think it should be brought up nonetheless. |
This comment has been minimized.
This comment has been minimized.
Glad to hear it!
There are several reasons to make it a lint. Most importantly, portability is not such a crucial property that we want to make strong guarantees and issue hard errors around it, I think. Being able to opt out of this checking seems desirable. Lints also give us greater flexibility to change the errors produced over time, which the RFC talks a bit about. It's also worth saying that there's not a fundamental distinction between lints and type checking; we can do arbitrary static analysis in the lint.
The portability is literally the
I'm not sure! Part of the reason I only sketched out the rollout for
Metadata will be recorded for portability in upstream crates, and thus checked in downstream crates.
Will do. |
This comment has been minimized.
This comment has been minimized.
|
Thanks to those who've already provided feedback! It looks like there's more interest in tracking trait usage than I expected. I left this out in the initial proposal because it didn't seem worth the complexity. I do think that limiting |
This comment has been minimized.
This comment has been minimized.
|
What about |
This comment has been minimized.
This comment has been minimized.
|
In general: I like it! A few thoughts though:
|
This comment has been minimized.
This comment has been minimized.
|
Thanks for writing this up @aturon! I know it's been a long haul getting here but I'm quite excited to see this RFC as I think it strikes a great balance with all the thoughts we've heard so far. One thing I always find useful is to game out common usage patterns and see what they would look like. First I figured it'd be good to take a look at the approach the standard library takes in the definition of In pub struct File {
inner: imp::File,
}
impl File {
pub fn cross_platform_method(&self) {
self.inner.cross_platform_method()
}
}The actual definition of
I think that the most ergonomic solution for this would be to disable the lint inside these modules, right? Basically In terms of other concrete cases I like the ergonomics of writing a platform-specific API. Once you're in a I personally feel that Ideally I think we could add a definition like https://is.gd/qvrAm0 which basically just expands to a bunch of cfg expressions on statements. The crucial piece that While writing this comment though @Kimundi brought up an interesting point that it might be pretty awesome if we can auto-detect something like this. I would imagine it's very hard to do (running resolution multiple times... kinda), but the benefits would be that my example above wouldn't need any modifications either. In terms of subsets, I feel like that's where the RFC gets very hand-wavy. Splicing out threads, compiling for emscripten, or trying to compile out floats I feel will need more thought than just applying the
|
japaric
reviewed
Jan 24, 2017
| `try_push` method provided via an extension trait). It's not clear how to do | ||
| that with the current [facade] setup. | ||
|
|
||
| * Kernels and embedded environments often want to |
This comment has been minimized.
This comment has been minimized.
japaric
Jan 24, 2017
Member
I thought this was already solved? Using the right codegen options removed the LLVM assertions that x86 devs were seeing by lowering float operations to intrinsics (software implementations) without having to modify the core crate. cc @steveklabnik @phil-opp Does the #rust-osdev community still recommend patching core to remove floats?
On the ARM Cortex-M side of embedded development, if your hardware doesn't have floating point instructions then you use, for example, the thumbv7em-none-eabi target instead of the "hard float" variant: thumbv7em-none-eabihf, which does have floating point instructions. No need to strip anything from the core crate.
This comment has been minimized.
This comment has been minimized.
steveklabnik
Jan 24, 2017
Member
Most people use xargo with the right options, yes. Or at least, I do, @phil-opp does, most people I've talked to do.
That still doesn't mean that it wouldn't be nice to have a more real solution here; IIRC, with the codegen options we use, if we use floating point, we get software emulation; I'd rather just straight-up outlaw it.
This comment has been minimized.
This comment has been minimized.
Ericson2314
Jan 25, 2017
Contributor
@japaric still seems nice to allow people to float altogether if they don't need it, rather than fallback on soft-float but hope it isn't used.
This comment has been minimized.
This comment has been minimized.
|
@alexcrichton : I had the impression that implementing the auto detection would not be all that much harder a what has been proposed already. Basically, you'd just look for matching sets of differently-cfgs items with the same nominal names. What I can see as problematic is the need to descent down into each of them - but it sems like that is already a inherent complexity of the RFC as is? |
This comment has been minimized.
This comment has been minimized.
|
Wow; I'm really excited for this! I had a few times rambled about SAT for this, but, well, wasn't sure who heard. Three points that no one has brought up here yet:
All in all, I'm ecstatic :) |
This comment has been minimized.
This comment has been minimized.
Wanted to add that:
|
This comment has been minimized.
This comment has been minimized.
|
@pornel What kind of code patterns require |
This comment has been minimized.
This comment has been minimized.
|
@joshtriplett In general I'm really disappointed that Rust has very poor support for integer sizes other than I've tried using smaller, more efficient types, but amount of dangerous casting back and forth is unmaintainable. |
This comment has been minimized.
This comment has been minimized.
File sizes should use a Rust equivalent of |
This comment has been minimized.
This comment has been minimized.
|
@Kimundi I'm personally under the impression that inferring |
This comment has been minimized.
This comment has been minimized.
|
@newpavlov Sorry I missed that before. I do think we may want to layer on such a system over time, but I'd personally prefer to start at the |
This comment has been minimized.
This comment has been minimized.
|
@aturon: I guess what I haven't really realized is that most situations in which this lint can miss portability issues are also situations where you either would get compiler errors on the problematic platforms anyway, or where all supported platforms already mutually imply portability considerations because there is a However, I think there is one scenario where this issue still applies: Platform-specific optional code. For example: // mycrate.rs
pub fn mycrate_function() {
// ...
}
#[cfg(windows)]
pub fn windows_specific_mycrate_function() {
// ...
windows_more_specific_mycrate_function();
}
#[cfg(all(windows, target_pointer_width = "64"))]
pub fn windows_more_specific_mycrate_function() {
// ...
}Compiling this on Of course the real issue here is that the crate author has written platform-specific code without testing it himself, but I can see that happening in practice due to things like CI services testing the code on all platforms, but not necessarily informing the author about warnings. |
This comment has been minimized.
This comment has been minimized.
That seems like the primary value here: taking an error you'd normally only see if you attempted to target that platform, and turning it into a lint you'll see on a platform where it'd otherwise pass silently. That helps people think more consciously about portability and avoid unintentional non-portability just because it works on the platform in front of them. |
This comment has been minimized.
This comment has been minimized.
|
@Kimundi I just pushed an additional commit to try to clarify the questions you were raising (which also includes your example). Please let me know if there are further issues that need to be addressed. |
This comment has been minimized.
This comment has been minimized.
|
@aturon: Looks good! |
This comment has been minimized.
This comment has been minimized.
|
In this section of the design it says that, faced with a non-portability warning, you can do one of three things:
But what if this crate is actually a non-portable crate? Take nix for example, which is an inherently UNIX specific crate. Do you take the third option? But the third option seems designed around the assumption that your code is actually portable, which is why it is simply |
This comment has been minimized.
This comment has been minimized.
|
@withoutboats Doesn't this solve your question? Use the 2nd option? #![cfg(unix)] |
This comment has been minimized.
This comment has been minimized.
|
@withoutboats -- @kennytm's comment is basically what I had in mind for platform-specific crates. There is an interesting question, though, about whether we need finer-grained allows in general for the lint. Personally I'd be inclined to punt on this for now, and see what happens in practice; we can easily add such a mechanism if needed. |
This comment has been minimized.
This comment has been minimized.
|
I guess that makes sense! I wonder if there's a better error message we could provide than just having the crate have no items in it though (that is, if you use a unix crate on windows). In any event that's the kind of minor improvement we can iterate on, marking my check. |
rfcbot
added
the
final-comment-period
label
Apr 17, 2017
This comment has been minimized.
This comment has been minimized.
rfcbot
commented
Apr 17, 2017
|
|
carols10cents
added this to FCP
in Tracker
Apr 26, 2017
This comment has been minimized.
This comment has been minimized.
rfcbot
commented
Apr 27, 2017
|
The final comment period is now complete. |
This was referenced Apr 29, 2017
aturon
merged commit 1c676d6
into
rust-lang:master
Apr 29, 2017
This comment has been minimized.
This comment has been minimized.
|
This RFC has now been merged! Further discussion should happen on the tracking issue. Please let me know if you're interested in working on the implementation. |
aturon
referenced this pull request
Jul 21, 2017
Closed
containers should provide some way to not panic on failed allocations #29802
kennytm
referenced this pull request
Jul 28, 2017
Merged
Expose all OS-specific modules in libstd doc. #43348
pepyakin
referenced this pull request
Nov 21, 2017
Closed
Tracking issue for supporting asm.js and WebAssembly without Fastcomp #44006
This comment has been minimized.
This comment has been minimized.
|
Once we get |
aturon commentedJan 23, 2017
•
edited
There has long been a desire to expand the number of platform- and architecture-specific APIs in the standard library, and to offer subsets of the standard library for working in constrained environments. At the same time, we want to retain the property that Rust code is portable by default.
This RFC proposes a new portability lint, which threads the needle between these two desires. The lint piggybacks on the existing
cfgsystem, so that using APIs involvingcfgwill generate a warning unless there is explicit acknowledgment of the portability implications.The lint is intended to make the existing
std::osmodule obsolete, to allow expansion (and subsetting) of the standard library, and to provide deeper checking for portability across the ecosystem.Rendered