@@ -329,3 +329,171 @@ same API with different algorithms tuned for different use-cases.
329
329
There are cases where the same coherence properties may be checked multiple
330
330
times. Perhaps these checks could be memoized with the incremental machinery to
331
331
avoid the redundancy.
332
+
333
+ # Appendix - Alloy spec (WIP)
334
+
335
+ ---
336
+ title: Alloy spec for Rust implementation coherency
337
+ ---
338
+
339
+ Note: this is a literate [ Alloy] ( https://alloytools.org/ ) spec. Download the
340
+ most recent version of Alloy from
341
+ https://github.com/AlloyTools/org.alloytools.alloy/releases , and see
342
+ https://alloy.readthedocs.io for documentation.
343
+
344
+ # Alloy spec for Rust implementation coherency
345
+
346
+ This is a simplified spec of Rust coherency checking. Rust requires that there,
347
+ are no overlapping or conflicting implementations with a complete Rust program,
348
+ as that would allow for ambiguity about which one to use.
349
+
350
+ (This applies to all implementations, but here we're only going to consider the
351
+ subset of simple trait implementations for types, with no generic type
352
+ parameters.)
353
+
354
+ The current solution to this is to requires that when a crate implements a trait
355
+ for a type, at least one of the type or the trait have to be "local" - ie,
356
+ defined in the same crate.
357
+
358
+ This is simple to describe and implement, but in practice it has several downsides:
359
+ - (dependencies, no third-party impls)
360
+
361
+ ## Alloy Spec
362
+
363
+ First we define signatures for types and traits. Both are defined in a crate:
364
+
365
+ ``` alloy
366
+ sig Trait {
367
+ trait_def: one Crate
368
+ }
369
+
370
+ sig Type {
371
+ type_def: one Crate
372
+ }
373
+ ```
374
+
375
+ And crates themselves. Crates can depend on a set of other crates, but the
376
+ overall crate dependency graph must be acyclic. Each crate also has a relation
377
+ of which trait implementations for which types it contains.
378
+
379
+ ``` alloy
380
+ sig Crate {
381
+ deps: set Crate,
382
+ impls: Trait -> Type,
383
+ } {
384
+ no this & this.^@deps -- acyclic
385
+ }
386
+ ```
387
+
388
+ A Binary is the unique "top-level" crate which depends on all the other crates
389
+ transitively. Or to put it another way, no other crate depends on Binary.
390
+
391
+ ``` alloy
392
+ one sig Binary extends Crate {} {
393
+ no @deps.this -- nothing depends on Binary
394
+ all c: Crate - Binary | c in this.^@deps -- Binary depends on everything else
395
+ }
396
+ ```
397
+
398
+ Let's define the safety invariant we need to enforce, that every implementation
399
+ is unique. Or more precisely, for every trait/type pair, there's at most one
400
+ crate implementing it. (It's fine if nothing implements it.)
401
+
402
+ ``` alloy
403
+ pred coherent_impls[crates: Crate] {
404
+ all tr: Trait, ty: Type | lone crates & impls.ty.tr
405
+ }
406
+ ``````
407
+
408
+ This is the basic orphan rule, with a tight definition of "local": either the
409
+ type or the trait must be defined in the crate:
410
+
411
+ ```alloy
412
+ pred local_orphan_rule[crates: Crate] {
413
+ all crate: crates |
414
+ crate.impls in
415
+ (crate[trait_def] -> (crate + crate.deps)[type_def]) +
416
+ ((crate + crate.deps)[trait_def] -> crate[type_def])
417
+ }
418
+ ```
419
+
420
+ We can check that if ` local_orphan_rule ` is true for all crates, then we have
421
+ coherence for all crates. This has no counter-examples.
422
+
423
+ ``` alloy
424
+ check local_coherent {
425
+ -- ie, checking local_orphan_rule on each crate implies that all crates are globally coherent
426
+ local_orphan_rule[Crate] => coherent_impls[Crate]
427
+ }
428
+ ```
429
+
430
+ ## impl dependencies
431
+
432
+ Let's extend the orphan constraint so that the definition of "local" is extended
433
+ to immediate dependencies as well. In a way this is simpler than
434
+ ` local_orphan_rule ` because we no longer have to constrain either the type or
435
+ trait to be in ` crate ` .
436
+
437
+ ``` alloy
438
+ pred dep_orphan_rule[crates: Crate] {
439
+ all crate: crates |
440
+ crate.impls in
441
+ (crate + crate.deps)[trait_def] -> (crate + crate.deps)[type_def]
442
+ }
443
+ ```
444
+
445
+ However, this is not sufficient to maintain the invariant. This will quickly
446
+ find a counter-example with two crates with a duplicate implementation.
447
+
448
+ ``` alloy
449
+ check dep_coherent_bad {
450
+ dep_orphan_rule[Crate] => coherent_impls[Crate]
451
+ }
452
+ ```
453
+
454
+ We need to add additional constraints to maintain the invariant. First, let's
455
+ define a function which, for a given crate which defines types and/or traits,
456
+ all the crates with implementations for those definitions:
457
+ ``` alloy
458
+ fun impl_crates[c: Crate]: set Crate {
459
+ c[trait_def][impls.univ] + c[type_def][impls].univ
460
+ }
461
+ ```
462
+
463
+ We can then apply the constraint in the most general way: for all dependencies
464
+ of Binary, all the crates implementing anything must be coherent. We'll ignore
465
+ that this is a tautology for now, as we'll tighten this up later.
466
+
467
+ ``` alloy
468
+ check dep_coherent_impl_crates {
469
+ {
470
+ dep_orphan_rule[Crate] -- redundant
471
+ all dep: Binary.*deps |
472
+ coherent_impls[impl_crates[dep]] -- tautology
473
+ } => coherent_impls[Crate]
474
+ } for 10 -- make sure there's enough objects to be interesting
475
+ ```
476
+
477
+ Unfortunately, this doesn't correspond to how the build is actually performed in
478
+ practice. When we're compiling a crate which has definitions, we don't know
479
+ which crates will have implementations. And when we're compiling the crates with
480
+ implementations, we don't know which other crate to cross-check with for
481
+ coherence.
482
+
483
+ The key insight is that there must be * some* crate which has both the
484
+ implementing crates in its transitive dependencies (even if it's the top-level
485
+ Binary), which means it can check for coherence when compiling that crate.
486
+
487
+ For example, here ` Use ` is responsible for checking the coherence of ` ImplA ` and
488
+ ` ImplB ` in its dependencies, but ` ImplC ` someone else's problem, and ` Other ` is
489
+ not relevant.
490
+ ``` mermaid
491
+ graph TD
492
+ Use --> ImplA & ImplB
493
+ ImplA & ImplB & ImplC --> Defn
494
+ Use --> Other
495
+ ```
496
+
497
+ ``` alloy
498
+ -- TODO check constraint for common deps
499
+ ```
0 commit comments