v0

rf A write-capable slice is unique unless opted out.

At a callable boundary, a write-capable slice is unique unless you opt out. Two readonly slices may share. That is the language. Everything else is a call site.

scale.rf
// writer + reader. Obligation: in ⊥ out.
scale(out: writeonly []r32, in: readonly []r32, s: r32) {
    for i in 0..out.len {
        out[i] = in[i] * s
    }
}

Four procedures.

And the call sites that must accept or reject. That is the whole v0 suite.

rf
// scale.rf — writer + reader. Obligation: in ⊥ out.
scale(out: writeonly []r32, in: readonly []r32, s: r32) {
    for i in 0..out.len {
        out[i] = in[i] * s
    }
}

// dot.rf — reader + reader. No obligation.
dot(a: readonly []r32, b: readonly []r32) -> r32 {
    sum: r32 = 0
    for i in 0..a.len {
        sum = sum + a[i] * b[i]
    }
    return sum
}

// add.rf — writer + writer. Obligation: a ⊥ b.
add(a: readwrite []r32, b: readwrite []r32) {
    for i in 0..a.len {
        a[i] = a[i] + b[i]
    }
}

// add_overlap.rf — same body, obligation cancelled.
#allow alias(a, b)
add_overlap(a: readwrite []r32, b: readwrite []r32) {
    for i in 0..a.len {
        a[i] = a[i] + b[i]
    }
}
scale writer + reader in ⊥ out
dot reader + reader no obligation
add writer + writer a ⊥ b
add_overlap writer + writer #allow alias

The call sites.

Cases 2 and 5 are the hypothesis. Case 3 is the control that proves you did not make every pair unique.

1
scale(ys, xs, 2)

different allocs, writer + reader

accept
2
scale(xs, xs, 2)

same alloc, writer + reader

reject
3
_ = dot(xs, xs)

same alloc, reader + reader

accept
4
add(xs, ys)

different allocs, writer + writer

accept
5
add(xs, xs)

same alloc, writer + writer

reject
6
add_overlap(xs, xs)

same alloc, explicit opt-out

accept
7
// scale_inplace(xs, 2)

one name, in-place — a single readwrite, no pair

accept
cases.rf
main() {
    xs: []r32 = alloc(r32, 4)
    ys: []r32 = alloc(r32, 4)

    // 1. different allocs, writer + reader  → accept
    scale(ys, xs, 2)

    // 2. same alloc, writer + reader         → REJECT
    scale(xs, xs, 2)

    // 3. same alloc, reader + reader         → accept
    _ = dot(xs, xs)

    // 4. different allocs, writer + writer   → accept
    add(xs, ys)

    // 5. same alloc, writer + writer         → REJECT
    add(xs, xs)

    // 6. same alloc, explicit opt-out        → accept
    add_overlap(xs, xs)

    // 7. one name, in-place                  → accept (no pair)
    //    (would be: scale_inplace(xs, 2) with a single readwrite)
}

2 and 5 are compile errors that name the overlapping pair. 1, 3, 4, and 6 compile. When the proof succeeds, scale's x86 does not reload in[i] after out[i] = ….

C twins, same names.

C will compile scale(xs, xs, n, 2) with restrict and call it your problem. rf must refuse it.

rf — refuses the overlapping call
scale(out: writeonly []r32, in: readonly []r32, s: r32)

scale(ys, xs, 2)    // accept: different allocs
scale(xs, xs, 2)    // REJECT: names the overlapping pair
c — compiles it, your problem
void scale(float *restrict out, const float *restrict in, int n, float s)

scale(ys, xs, n, 2);    // compiles
scale(xs, xs, n, 2);    // also compiles
c
void scale(float *restrict out, const float *restrict in, int n, float s) {
    for (int i = 0; i < n; i++) out[i] = in[i] * s;
}

float dot(const float *a, const float *b, int n) {
    float sum = 0;
    for (int i = 0; i < n; i++) sum += a[i] * b[i];
    return sum;
}

void add(float *restrict a, float *restrict b, int n) {
    for (int i = 0; i < n; i++) a[i] += b[i];
}

void add_overlap(float *a, float *b, int n) { /* no restrict */
    for (int i = 0; i < n; i++) a[i] += b[i];
}

Get early access.

rf isn't public yet. Join the waitlist and we'll let you know when the first compiler drops.

No spam. One email when it ships.