Fortis Agent mode

Human mode: Appendix C: Lowering to Rust

Lowering reference and the Rust-expressible subset (LR C)#

Agent mode. Human mode: Lowering to Rust. Source of truth: LR C, LR 7.10.

Rules#

RuleStatementLR
LANG-07-03No clone, reference count or hidden shared ownership is ever inserted to repair a rejected program.LR 7.10, LR C.1
LANG-07-09An ownership shape with no lowering is rejected with E-OWN-090 (or the earlier rule's more specific code), never lowered with unsafe or lifetime erasure.LR 7.10, LR C.2
LANG-11-03noalloc is decided by the allocation whitelist of LR C.3 against the actual target and telemetry plan.LR 11.4, LR C.3

Generated application crates carry #![forbid(unsafe_code)]; a rustc rejection of frontend-accepted code is a compiler defect only inside the Rust-checked fragment (LR 7.10, LR C.1).

Shapes (C.1)#

ConstructNative shapeRust-checkedFrontend-checkedLR
type newtypestruct Name(Repr), private fieldnominal identityrefinement invariantLR C.1
classstruct + implfield privacygenerated-constructor visibilityLR C.1
trivial typeCopy struct/enumCopythe trivial tableLR C.1
copy pClone::clone of a generated Clonecopyable set; allocationLR C.1
read loan / exclusive loan&T / &mut Tloan validitylexical end (stricter); field-freezingLR C.1
move pmoveuse after moveLR C.1
from returnfn f<'a>(x: &'a T) -> &'a Ulifetimesingle originLR C.1
standard view / cursor&'a str, &'a [T], Cursor<'a, T> owned by its holderlifetimestandard types only (X-137)LR C.1
Shared[T] / Weak[T]Arc<T> / Weak<T>Send/Syncretain visibilityLR C.1
Mutex[T]std::sync::Mutex wrapperSend/Syncguard across awaitLR C.1
interface / dynamic Itrait + generics / Box<dyn I>, &dyn I, Arc<dyn I>trait bounds; object safetysubstitutability, coherence; dynamic eligibility, allocationLR C.1
mixingenerated inherent methodsconflicts, required methodsLR C.1
lambdaclosure, concrete environmentFn/FnMut/FnOncedeclared kind ≥ needed kind; capture listLR C.1
try func / catchResult<T, Failure> / matchlinearity; recover/return semanticsLR C.1
T? / matchOption<T> / matchexhaustiveness (both)binding ownershipLR C.1
async func / scopeasync fn state machine / join set polled by the parentlinear futures, cancellation at await; join on exitLR C.1
launchParallelpool spawn with 'static + SendSendShareableLR C.1
deferbody inlined at every exit (not Drop)each inlined copy's borrowsplaces initialised at every exitLR C.1
cleanupimpl Drop calling the methoddrop ordereffects contributedLR C.1
noalloc, effectsno representationwhitelist (C.3); summariesLR C.1
capture/recordframe struct writes, record callsdefinite assignmentLR C.1
capabilityzero-sized or handle struct, private constructorio requires capabilityLR C.1

Unsupported shapes (C.2)#

ShapeCodeAlternativeLR
loan from two parametersE-SYN-000return owned, or splitLR C.2, LR 7.6
class storing a loan (lifetime parameter, loan or loan-carrying field other than a loan from static)E-OWN-053own, copy, Shared, or pass per callLR C.2, LR 7.7
self-referential classE-OWN-053indices, or Shared + WeakLR C.2, LR 7.7
loan stored in Shared/MutexE-OWN-090own the valueLR C.2
fn mut escaping into SharedE-OWN-090Mutex around the state + fn closureLR C.2
future holding a loan on a parallel poolE-ASY-003pass owned dataLR C.2, LR 12.7
partial moveE-OWN-040move the whole, or restructureLR C.2, LR 7.2

A shape an earlier rule excludes keeps that rule's code (X-126); E-OWN-090 is for shapes only the lowering identifies (LR C.2).

Allocation whitelist (C.3)#

Allocation-freeAllocatingLR
arithmetic, comparison; field and array access; trivial and inline values (Array, FixedList, FixedMap, bounded Failure); a class or record built from trivial, moved, fresh, call-returned or retained (retain(), downgrade()) values; read/exclusive loans; calls of non-alloc functions; match; defer; join(a, b); capture into a bounded frameList/String/Map growth; Shared.create; dynamic creation; copy of a heap-owning value; boxed futures of dynamic async calls; extended failure payloads; launchParallel/launchBlocking; every cooperative launch in v1 (no inline task storage, X-125)LR C.3

Diagnostics#

CodeTriggerCorrectionLR
E-OWN-040move p.field while p still owns the restmove the whole binding; consume it with let Name(field: x, …) = move pLR 7.2, LR C.2
E-OWN-053lifetime parameter, or a field of a loan or loan-carrying type (not from static)own the data (String for StringView), copy it, Shared, or pass it per callLR 7.7, LR C.2
E-OWN-090a shape with no lowering (loan or fn mut into Shared/Mutex)own the value; Mutex or atomic state with a fn closureLR 7.10, LR C.2
E-ASY-003a pool launch whose future borrows or holds a non-Shareable valuepass owned Shareable data (move, copy)LR 12.7, LR C.2
E-EFF-001an allocating pattern in a noalloc bodyremove the allocation (a caller-provided buffer, an inline Array/FixedList); removing the claim is an API change, never a correction (LR 11.3)LR 11.1, LR 11.3, LR C.3

Correction patterns#

E-OWN-040: partial move (LR 7.2, LR C.2)#

Fortis
// reject: E-OWN-040
class Order {
  export id: Int
  export lines: List[Int]
}

test "lines" {
  let order = Order(id: 7, lines: List.of(1, 2, 3))
  let lines = move order.lines        // reject: E-OWN-040
  expect lines.count() == 3
}
Fortis
// accept
class Order {
  export id: Int
  export lines: List[Int]
}

test "lines" {
  let order = Order(id: 7, lines: List.of(1, 2, 3))
  let Order(id: id, lines: lines) = move order
  expect id == 7
  expect lines.count() == 3
}

E-OWN-053: stored loan (LR 7.7, LR C.2)#

Fortis
// reject: E-OWN-053
class Line {                          // reject: E-OWN-053
  export text: StringView
}

test "line" {
  expect 1 == 1
}
Fortis
// accept
class Line {
  export text: String
}

test "line" {
  let line = Line(text: "first")
  expect line.text == "first"
}

E-OWN-090: fn mut into Shared (LR C.2)#

Fortis
// reject: E-OWN-090
test "bump" {
  let mut count = 0
  let bump = fn mut[editable count]() Void {
    count = Int.saturatingAdd(count, 1)
  }
  let shared = Shared.create(move bump)     // reject: E-OWN-090
}
Fortis
// accept
test "bump" {
  let counter = Shared.create(AtomicInt.create(0))
  let handle = counter.retain()
  let bump = fn[move handle]() Void {
    discard handle.fetchAdd(1)
  }
  bump()
  bump()
  expect counter.load() == 2
}

E-ASY-003: loan sent to a pool (LR 12.7, LR C.2)#

Fortis
// reject: E-ASY-003
import fortis.host { Host }

async func total(items: ListView[Int]) Int {
  let mut sum = 0
  for x in items {
    sum = Int.saturatingAdd(sum, x)
  }
  return sum
}

export async func main(args: List[String]) Void {
  let caps = Host.capabilities()
  let numbers = List.of(1, 2, 3)
  scope tasks {
    let t = tasks.launchParallel(total(numbers))   // reject: E-ASY-003
    let sum = await t
    caps.console.print("${sum}")
  }
}
Fortis
// accept
import fortis.host { Host }

async func total(items: owned List[Int]) Int {
  let mut sum = 0
  for x in items {
    sum = Int.saturatingAdd(sum, x)
  }
  return sum
}

export async func main(args: List[String]) Void {
  let caps = Host.capabilities()
  let numbers = List.of(1, 2, 3)
  scope tasks {
    let t = tasks.launchParallel(total(move numbers))
    let sum = await t
    caps.console.print("${sum}")
  }
}
output
6

E-EFF-001: allocation in noalloc (LR 11.1, LR C.3)#

The record is reported at the allocating statement.

Fortis
// reject: E-EFF-001
pure noalloc func grow(items: editable List[Int]) Void {
  editable items.push(1)              // reject: E-EFF-001
}

test "grow" {
  expect 1 == 1
}
Fortis
// accept
pure func grow(items: editable List[Int]) Void {
  editable items.push(1)
}

pure noalloc func square(n: Int<-1000..1000>) Int => n * n

test "grow" {
  let mut items: List[Int] = List.default()
  grow(editable items)
  expect items.count() == 1
  expect square(12) == 144
}