1
- - Feature Name: N/A
1
+ - Feature Name: nonportable
2
2
- Start Date: 2016-11-15
3
3
- RFC PR: (leave this empty)
4
4
- Rust Issue: (leave this empty)
@@ -211,8 +211,10 @@ make the warning go away):
211
211
well (which will flag that function as Unix-specific).
212
212
213
213
- Decide to use the API * in a cross-platform way* , e.g. by providing a Windows
214
- version of the same functionality. In that case you can use a new macro,
215
- ` match_cfg ` , to prove to the lint that you are covering all necessary cases.
214
+ version of the same functionality. In that case you ` allow ` the lint,
215
+ explicitly acknowledging that your code may involve platform-specific APIs but
216
+ claiming that all platforms of the current ` cfg ` are handled. (See the
217
+ appendix at the end for a possible extension that does more checking).
216
218
217
219
In code, we'd have:
218
220
@@ -232,19 +234,19 @@ fn unlabeled() {
232
234
/// /////////////////////////////////////////////////////////////////////////////
233
235
234
236
#[cfg(unix)]
235
- fn foo_unix () {
237
+ fn foo () {
236
238
// No warning: we're within code that assumes `unix`
237
239
let fd = File :: open (" foo.txt" ). unwrap (). as_raw_fd ();
238
240
}
239
241
240
242
#[cfg(windows)]
241
- fn foo_windows () {
243
+ fn foo () {
242
244
// No warning: we're within code that assumes `windows`
243
245
let handle = File :: open (" foo.txt" ). unwrap (). as_raw_handle ();
244
246
}
245
247
246
248
#[cfg(linux)]
247
- fn foo_linux () {
249
+ fn linux_only () {
248
250
// No warning: we're within code that assumes `linux`, which implies `unix`
249
251
let fd = File :: open (" foo.txt" ). unwrap (). as_raw_fd ();
250
252
}
@@ -253,14 +255,12 @@ fn foo_linux() {
253
255
// Code that provides a cross-platform abstraction
254
256
/// /////////////////////////////////////////////////////////////////////////////
255
257
256
- // No `cfg` label here; it's a cross-platform function.
258
+ // No `cfg` label here; it's a cross-platform function, which we claim
259
+ // via the `allow`
260
+ #[allow(nonportable)]
257
261
fn cross_platform () {
258
- // No warning emitted, since handling both `unix` and `windows` means
259
- // this code works for all mainstream platforms.
260
- match_cfg! {
261
- unix => foo_unix (),
262
- windows => foo_windows (),
263
- }
262
+ // invoke an item with a more restrictive `cfg`
263
+ foo ()
264
264
}
265
265
```
266
266
@@ -277,8 +277,7 @@ speaking, items that are labeled with a given `cfg` assumption can only be used
277
277
within code making that same ` cfg ` assumption.
278
278
279
279
More precisely, each item has a * portability* , consisting of all the
280
- lexically-nested uses of ` cfg ` , including those expressed via ` match_cfg ` , but
281
- excluding mentions of Cargo features. If there are multiple uses of ` cfg ` , the
280
+ lexically-nested uses of ` cfg ` . If there are multiple uses of ` cfg ` , the
282
281
portability is taken to be their * conjunction* :
283
282
284
283
``` rust
@@ -291,6 +290,10 @@ mod foo {
291
290
}
292
291
```
293
292
293
+ The portability only considers built-in ` cfg ` attributes (like ` target_os ` ),
294
+ * not* Cargo features (which are treated as automatically true for the lint
295
+ purposes).
296
+
294
297
The lint is then straightforward to define at a high level: it walks over item
295
298
definitions and checks that the item's portability is * narrower* than the
296
299
portability of items it references or invokes. For example, ` bar ` in the above
@@ -317,16 +320,16 @@ specific to our use-case. In the limit, there are also many well-known
317
320
techniques for solving SAT efficiently even on very large examples that arise in
318
321
real-world usage.
319
322
320
- #### Known implications
323
+ #### Axioms
321
324
322
325
Another aspect of portability comparison is the relationship between things like
323
- ` unix ` and ` linux ` . In logical terms, we want to say that ` linux ` implies
326
+ ` unix ` and ` linux ` . In logical terms, we want to assume that ` linux ` implies
324
327
` unix ` , for example.
325
328
326
329
The primitive portabilities we'll be comparing are all * built in* (since we are
327
- not including Cargo features). We can thus build in implications between these
328
- built-in portabilities, which are fed to the SAT solver when comparing
329
- portabilities. The end result is that code like the following should pass the lint:
330
+ not including Cargo features). The solver can thus build in a number of
331
+ assumptions about these portabilities. The end result is that code like the
332
+ following should pass the lint:
330
333
331
334
``` rust
332
335
#[cfg(unix)]
@@ -339,6 +342,9 @@ fn linux_only() {
339
342
}
340
343
```
341
344
345
+ The precise details of how these implications are specified---and what
346
+ implications are desired---are left as implementation details.
347
+
342
348
### Determining the portability of referenced items
343
349
344
350
** How is the portability of a referenced item determined?** The lint will
@@ -373,48 +379,6 @@ fn invoke() {
373
379
}
374
380
```
375
381
376
- #### ` match_cfg `
377
-
378
- The ` match_cfg ` macro takes a sequence of ` cfg ` patterns, followed by ` => ` and
379
- an expression. Its syntax and semantics resembles that of ` match ` . However,
380
- there are some special considerations when checking portability:
381
-
382
- * When descending into an arm of a ` match_cfg ` , the arm is checked against
383
- portability that includes the pattern for the arm.
384
-
385
- * The portability for the ` match_cfg ` itself is understood as `any(p1, ...,
386
- p_n)` where the ` match_cfg` patterns are ` p1` through ` p_n`.
387
-
388
- Thus, for example, the following code will pass the lint:
389
-
390
- ``` rust
391
- #[cfg(windows)]
392
- fn windows_only () { .. }
393
-
394
- #[cfg(unix)]
395
- fn unix_only () { .. }
396
-
397
- #[cfg(any(windows, unix))]
398
- fn portable () {
399
- // the expression here has portability `any(windows, unix)`
400
- match_cfg! {
401
- windows => {
402
- // allowed because we are within a scope with
403
- // portability `all(any(windows, unix), windows)`
404
- windows_only ()
405
- }
406
- unix => {
407
- // allowed because we are within a scope with
408
- // portability `all(any(windows, unix), unix)`
409
- unix_only ()
410
- }
411
- }
412
- }
413
- ```
414
-
415
- If you have a ` match_case ` that covers * all* cases (like ` windows ` and
416
- ` not(windows) ` ), then it imposes * no* portability constraints on its context.
417
-
418
382
## The story for ` std `
419
383
420
384
With these basic mechanisms in hand, let's sketch out how we might apply them to
@@ -568,3 +532,59 @@ approach such cases before landing the RFC.
568
532
569
533
To what extent does this proposal obviate the need for the ` std ` facade? Might
570
534
it be possible to deprecate ` libcore ` in favor of the "subsetting ` std ` " approach?
535
+
536
+ # Appendix: possible extensions
537
+
538
+ ## ` match_cfg `
539
+
540
+ The original version of this RFC was more expansive, and proposed a ` match_cfg `
541
+ macro that provided some additional checking.
542
+
543
+ The ` match_cfg ` macro takes a sequence of ` cfg ` patterns, followed by ` => ` and
544
+ an expression. Its syntax and semantics resembles that of ` match ` . However,
545
+ there are some special considerations when checking portability:
546
+
547
+ * When descending into an arm of a ` match_cfg ` , the arm is checked against
548
+ portability that includes the pattern for the arm.
549
+
550
+ * The portability for the ` match_cfg ` itself is understood as `any(p1, ...,
551
+ p_n)` where the ` match_cfg` patterns are ` p1` through ` p_n`.
552
+
553
+ Thus, for example, the following code will pass the lint:
554
+
555
+ ``` rust
556
+ #[cfg(windows)]
557
+ fn windows_only () { .. }
558
+
559
+ #[cfg(unix)]
560
+ fn unix_only () { .. }
561
+
562
+ #[cfg(any(windows, unix))]
563
+ fn portable () {
564
+ // the expression here has portability `any(windows, unix)`
565
+ match_cfg! {
566
+ windows => {
567
+ // allowed because we are within a scope with
568
+ // portability `all(any(windows, unix), windows)`
569
+ windows_only ()
570
+ }
571
+ unix => {
572
+ // allowed because we are within a scope with
573
+ // portability `all(any(windows, unix), unix)`
574
+ unix_only ()
575
+ }
576
+ }
577
+ }
578
+ ```
579
+
580
+ If you have a ` match_case ` that covers * all* cases (like ` windows ` and
581
+ ` not(windows) ` ), then it imposes * no* portability constraints on its context.
582
+
583
+ On more reflection, though, this extension doesn't seem so worthwhile: while it
584
+ provides some additional checking, the fact remains that only the
585
+ currently-enabled ` cfg ` is fully checked, so the additional guarantee you get is
586
+ somewhat mixed. It's also a rare (maybe non-existent) error to explicitly write
587
+ code that's broken down by platforms, but forget one of the platforms you wish
588
+ to cover.
589
+
590
+ We can, however, add ` match_cfg ` as a backwards-compatible extension at any time.
0 commit comments