nandi/rustnimpublic Fork
⑂ 8038a82
Commits
⬇ Clone ▾
git clone https://git.rickub.com/nandi/rustnim.git
git clone ssh://git@rickub.com/nandi/rustnim.git

Host key fingerprint (ed25519): SHA256:iycHnxEyq0Q7uyVpB7JlznP0G7JrTPXLYRcAU5CSLhc — verify it before your first connect.

Download ZIP

Lower `static` items, `Self(..)`, and atomics through a shared reference

`colorchoice` compiles: Rust through rustnim to Nim that `nim c` accepts. It
is the second of libcosmic's dependencies to go the whole way.

A `static` is a module-level binding. Rust const-evaluates its initialiser and
Nim runs it at startup, both before any code that can observe the value, so
the two agree on every program. `static mut` stays refused: that is
unsynchronised shared mutation, and Rust demands `unsafe` for it precisely
because it is not safe to reproduce casually.

Statics get their own pass, after the forward declarations. They have to come
before the procs that read them, and their initialisers routinely call a proc,
so neither end of the file will do.

`Self(x)` inside an `impl` is the tuple struct's constructor under the name
the impl gives it.

Rust's atomic `load`/`store` take `&self` -- operating through a shared
reference is the whole point of an atomic -- while Nim's want a mutable
location, so these go through the address.

They are templates rather than procs, and that distinction is the commit: as
procs they compiled, ran, and silently dropped every store, because a proc
parameter is a copy and `unsafeAddr` inside one takes the address of the copy.
`tests/cases/045` caught it -- 6 where rustc says 100.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
nandithebull committed 2026-09-20T10:56:21-07:00 Browse files
8038a82 parent: e28de7f
modified src/lower.rs +118 -4
@@ -704,6 +704,18 @@ impl Lowerer {
704704 self.blank();
705705 }
706706
707+ // Pass 3.5: `static` items. Module-level bindings, so Nim wants them
708+ // declared before the procs that read them -- and their initialisers
709+ // routinely call a proc, so they come after the forward declarations
710+ // that make those callable.
711+ for (m, f) in files {
712+ self.cur_mod = m.clone();
713+ for item in &f.items {
714+ let r = self.emit_statics(item);
715+ self.note(r)?;
716+ }
717+ }
718+
707719 self.body_start = self.out.len();
708720
709721 // Pass 4: bodies.
@@ -1007,6 +1019,13 @@ impl Lowerer {
10071019 }
10081020 self.impl_generics = outer;
10091021 }
1022+ Item::Static(st) => {
1023+ // The name and type, so a body reached before the emission
1024+ // pass can still be typed against it.
1025+ if let Ok(t) = self.map_ty(&st.ty) {
1026+ self.bind(&st.ident.to_string(), t.owned());
1027+ }
1028+ }
10101029 Item::Fn(f) => {
10111030 // A body may declare its own types, and a signature below may
10121031 // name one, so these come first.
@@ -2617,6 +2636,33 @@ impl Lowerer {
26172636 // --------------------------------------------------------------- items
26182637
26192638 /// Emit the type definitions only: they must precede every signature.
2639+ /// Write out the `static` items, after the types and before the procs.
2640+ fn emit_statics(&mut self, item: &Item) -> Result<(), String> {
2641+ if !self.cfg_keeps(item_attrs(item))? {
2642+ return Ok(());
2643+ }
2644+ match item {
2645+ Item::Static(_) => self.item_inner(item),
2646+ Item::Mod(m) => {
2647+ if let Some((_, items)) = &m.content {
2648+ for i in items {
2649+ self.emit_statics(i)?;
2650+ }
2651+ }
2652+ Ok(())
2653+ }
2654+ Item::Const(c) => {
2655+ if let Some(items) = derive_item_block(c) {
2656+ for i in &items {
2657+ self.emit_statics(i)?;
2658+ }
2659+ }
2660+ Ok(())
2661+ }
2662+ _ => Ok(()),
2663+ }
2664+ }
2665+
26202666 /// Write out every associated constant, ahead of the types.
26212667 fn emit_assoc_consts(&mut self, item: &Item) -> Result<(), String> {
26222668 if !self.cfg_keeps(item_attrs(item))? {
@@ -2732,8 +2778,11 @@ impl Lowerer {
27322778 return Ok(());
27332779 }
27342780 }
2735- // Types were emitted in their own pass.
2736- if matches!(item, Item::Struct(_) | Item::Enum(_) | Item::Const(_)) {
2781+ // Types and statics were emitted in their own passes.
2782+ if matches!(
2783+ item,
2784+ Item::Struct(_) | Item::Enum(_) | Item::Const(_) | Item::Static(_)
2785+ ) {
27372786 return Ok(());
27382787 }
27392788 if matches!(item, Item::Macro(m) if path_name(&m.mac.path) == "bitflags")
@@ -2866,6 +2915,40 @@ impl Lowerer {
28662915 m.ident
28672916 ))
28682917 }
2918+ // A `static` is a module-level binding. Rust const-evaluates its
2919+ // initialiser and Nim runs it at startup; both happen before any
2920+ // code that can observe the value, so the two agree on every
2921+ // program. `static mut` stays refused -- that is unsynchronised
2922+ // shared mutation, and Rust requires `unsafe` for it precisely
2923+ // because it is not safe to reproduce casually.
2924+ Item::Static(st) => {
2925+ if matches!(st.mutability, syn::StaticMutability::Mut(_)) {
2926+ return Err(format!(
2927+ "`static mut {}` is unsynchronised shared mutable state; \
2928+ Rust requires `unsafe` to touch it and rustnim has no \
2929+ faithful equivalent",
2930+ st.ident
2931+ ));
2932+ }
2933+ let t = self.map_ty(&st.ty)?.owned();
2934+ let v = self.expr_at(&st.expr, Some(&t))?;
2935+ let star = if self.indent == 0 { "*" } else { "" };
2936+ // `let`, not `const`: the initialiser is a call more often
2937+ // than not, and Nim's `const` demands compile-time evaluation
2938+ // where Rust's `static` accepts anything const-*evaluable*.
2939+ self.line(&format!(
2940+ "let {}{}: {} = {}",
2941+ ident(&st.ident.to_string()),
2942+ star,
2943+ t.render(),
2944+ v.code
2945+ ));
2946+ self.bind(&st.ident.to_string(), t);
2947+ if self.indent == 0 {
2948+ self.blank();
2949+ }
2950+ Ok(())
2951+ }
28692952 other => Err(format!("unsupported item: {}", item_kind(other))),
28702953 }
28712954 }
@@ -7622,6 +7705,30 @@ impl Lowerer {
76227705 };
76237706 }
76247707
7708+ // `Self(x)` inside an `impl` is the tuple struct's constructor, under
7709+ // the name the impl gives it.
7710+ if name == "Self" {
7711+ if let Some(t) = self.self_ty.clone() {
7712+ let n = type_name(&t);
7713+ if let Some(fields) = self.structs.get(&n).cloned() {
7714+ let mut parts = Vec::new();
7715+ for (i, a) in c.args.iter().enumerate() {
7716+ let want = fields.get(i).map(|(_, t)| t.clone());
7717+ let v = self.expr_at(a, want.as_ref())?;
7718+ parts.push(format!(
7719+ "{}: {}",
7720+ ident(&fields[i].0),
7721+ v.code
7722+ ));
7723+ }
7724+ return Ok(Val::new(
7725+ format!("{}({})", ident(&n), parts.join(", ")),
7726+ Some(t),
7727+ ));
7728+ }
7729+ }
7730+ }
7731+
76257732 // A tuple enum variant applied to arguments: `Shape::Circle(1.0)`.
76267733 if let Some((def, v)) = self.resolve_variant(&p.path) {
76277734 let (ty, _) = self.variant_type(&def, expect)?;
@@ -8340,12 +8447,19 @@ impl Lowerer {
83408447 a.push(self.expr_at(raw, want.as_ref())?.code);
83418448 }
83428449 let nim = match name.as_str() {
8450+ // Through the address: Rust's atomics take `&self`, and
8451+ // Nim's `load`/`store` want a mutable location. See
8452+ // `rsAtomicLoad` in `src/prelude_sync.nim`.
83438453 "load" => return Ok(Val::new(
8344- format!("{}.load({})", recv.code, a.first().cloned().unwrap_or_default()),
8454+ format!(
8455+ "rsAtomicLoad({}, {})",
8456+ recv.code,
8457+ a.first().cloned().unwrap_or_default()
8458+ ),
83458459 Some(inner),
83468460 )),
83478461 "store" => return Ok(Val::new(
8348- format!("{}.store({}, {})", recv.code, a[0], a[1]),
8462+ format!("rsAtomicStore({}, {}, {})", recv.code, a[0], a[1]),
83498463 Some(Nim::Unit),
83508464 )),
83518465 "swap" => "exchange",
@@ -704,6 +704,18 @@ impl Lowerer {
704 self.blank();704 self.blank();
705 }705 }
706 706
707+ // Pass 3.5: `static` items. Module-level bindings, so Nim wants them
708+ // declared before the procs that read them -- and their initialisers
709+ // routinely call a proc, so they come after the forward declarations
710+ // that make those callable.
711+ for (m, f) in files {
712+ self.cur_mod = m.clone();
713+ for item in &f.items {
714+ let r = self.emit_statics(item);
715+ self.note(r)?;
716+ }
717+ }
718+
707 self.body_start = self.out.len();719 self.body_start = self.out.len();
708 720
709 // Pass 4: bodies.721 // Pass 4: bodies.
@@ -1007,6 +1019,13 @@ impl Lowerer {
1007 }1019 }
1008 self.impl_generics = outer;1020 self.impl_generics = outer;
1009 }1021 }
1022+ Item::Static(st) => {
1023+ // The name and type, so a body reached before the emission
1024+ // pass can still be typed against it.
1025+ if let Ok(t) = self.map_ty(&st.ty) {
1026+ self.bind(&st.ident.to_string(), t.owned());
1027+ }
1028+ }
1010 Item::Fn(f) => {1029 Item::Fn(f) => {
1011 // A body may declare its own types, and a signature below may1030 // A body may declare its own types, and a signature below may
1012 // name one, so these come first.1031 // name one, so these come first.
@@ -2617,6 +2636,33 @@ impl Lowerer {
2617 // --------------------------------------------------------------- items2636 // --------------------------------------------------------------- items
2618 2637
2619 /// Emit the type definitions only: they must precede every signature.2638 /// Emit the type definitions only: they must precede every signature.
2639+ /// Write out the `static` items, after the types and before the procs.
2640+ fn emit_statics(&mut self, item: &Item) -> Result<(), String> {
2641+ if !self.cfg_keeps(item_attrs(item))? {
2642+ return Ok(());
2643+ }
2644+ match item {
2645+ Item::Static(_) => self.item_inner(item),
2646+ Item::Mod(m) => {
2647+ if let Some((_, items)) = &m.content {
2648+ for i in items {
2649+ self.emit_statics(i)?;
2650+ }
2651+ }
2652+ Ok(())
2653+ }
2654+ Item::Const(c) => {
2655+ if let Some(items) = derive_item_block(c) {
2656+ for i in &items {
2657+ self.emit_statics(i)?;
2658+ }
2659+ }
2660+ Ok(())
2661+ }
2662+ _ => Ok(()),
2663+ }
2664+ }
2665+
2620 /// Write out every associated constant, ahead of the types.2666 /// Write out every associated constant, ahead of the types.
2621 fn emit_assoc_consts(&mut self, item: &Item) -> Result<(), String> {2667 fn emit_assoc_consts(&mut self, item: &Item) -> Result<(), String> {
2622 if !self.cfg_keeps(item_attrs(item))? {2668 if !self.cfg_keeps(item_attrs(item))? {
@@ -2732,8 +2778,11 @@ impl Lowerer {
2732 return Ok(());2778 return Ok(());
2733 }2779 }
2734 }2780 }
2735- // Types were emitted in their own pass.2781+ // Types and statics were emitted in their own passes.
2736- if matches!(item, Item::Struct(_) | Item::Enum(_) | Item::Const(_)) {2782+ if matches!(
2783+ item,
2784+ Item::Struct(_) | Item::Enum(_) | Item::Const(_) | Item::Static(_)
2785+ ) {
2737 return Ok(());2786 return Ok(());
2738 }2787 }
2739 if matches!(item, Item::Macro(m) if path_name(&m.mac.path) == "bitflags")2788 if matches!(item, Item::Macro(m) if path_name(&m.mac.path) == "bitflags")
@@ -2866,6 +2915,40 @@ impl Lowerer {
2866 m.ident2915 m.ident
2867 ))2916 ))
2868 }2917 }
2918+ // A `static` is a module-level binding. Rust const-evaluates its
2919+ // initialiser and Nim runs it at startup; both happen before any
2920+ // code that can observe the value, so the two agree on every
2921+ // program. `static mut` stays refused -- that is unsynchronised
2922+ // shared mutation, and Rust requires `unsafe` for it precisely
2923+ // because it is not safe to reproduce casually.
2924+ Item::Static(st) => {
2925+ if matches!(st.mutability, syn::StaticMutability::Mut(_)) {
2926+ return Err(format!(
2927+ "`static mut {}` is unsynchronised shared mutable state; \
2928+ Rust requires `unsafe` to touch it and rustnim has no \
2929+ faithful equivalent",
2930+ st.ident
2931+ ));
2932+ }
2933+ let t = self.map_ty(&st.ty)?.owned();
2934+ let v = self.expr_at(&st.expr, Some(&t))?;
2935+ let star = if self.indent == 0 { "*" } else { "" };
2936+ // `let`, not `const`: the initialiser is a call more often
2937+ // than not, and Nim's `const` demands compile-time evaluation
2938+ // where Rust's `static` accepts anything const-*evaluable*.
2939+ self.line(&format!(
2940+ "let {}{}: {} = {}",
2941+ ident(&st.ident.to_string()),
2942+ star,
2943+ t.render(),
2944+ v.code
2945+ ));
2946+ self.bind(&st.ident.to_string(), t);
2947+ if self.indent == 0 {
2948+ self.blank();
2949+ }
2950+ Ok(())
2951+ }
2869 other => Err(format!("unsupported item: {}", item_kind(other))),2952 other => Err(format!("unsupported item: {}", item_kind(other))),
2870 }2953 }
2871 }2954 }
@@ -7622,6 +7705,30 @@ impl Lowerer {
7622 };7705 };
7623 }7706 }
7624 7707
7708+ // `Self(x)` inside an `impl` is the tuple struct's constructor, under
7709+ // the name the impl gives it.
7710+ if name == "Self" {
7711+ if let Some(t) = self.self_ty.clone() {
7712+ let n = type_name(&t);
7713+ if let Some(fields) = self.structs.get(&n).cloned() {
7714+ let mut parts = Vec::new();
7715+ for (i, a) in c.args.iter().enumerate() {
7716+ let want = fields.get(i).map(|(_, t)| t.clone());
7717+ let v = self.expr_at(a, want.as_ref())?;
7718+ parts.push(format!(
7719+ "{}: {}",
7720+ ident(&fields[i].0),
7721+ v.code
7722+ ));
7723+ }
7724+ return Ok(Val::new(
7725+ format!("{}({})", ident(&n), parts.join(", ")),
7726+ Some(t),
7727+ ));
7728+ }
7729+ }
7730+ }
7731+
7625 // A tuple enum variant applied to arguments: `Shape::Circle(1.0)`.7732 // A tuple enum variant applied to arguments: `Shape::Circle(1.0)`.
7626 if let Some((def, v)) = self.resolve_variant(&p.path) {7733 if let Some((def, v)) = self.resolve_variant(&p.path) {
7627 let (ty, _) = self.variant_type(&def, expect)?;7734 let (ty, _) = self.variant_type(&def, expect)?;
@@ -8340,12 +8447,19 @@ impl Lowerer {
8340 a.push(self.expr_at(raw, want.as_ref())?.code);8447 a.push(self.expr_at(raw, want.as_ref())?.code);
8341 }8448 }
8342 let nim = match name.as_str() {8449 let nim = match name.as_str() {
8450+ // Through the address: Rust's atomics take `&self`, and
8451+ // Nim's `load`/`store` want a mutable location. See
8452+ // `rsAtomicLoad` in `src/prelude_sync.nim`.
8343 "load" => return Ok(Val::new(8453 "load" => return Ok(Val::new(
8344- format!("{}.load({})", recv.code, a.first().cloned().unwrap_or_default()),8454+ format!(
8455+ "rsAtomicLoad({}, {})",
8456+ recv.code,
8457+ a.first().cloned().unwrap_or_default()
8458+ ),
8345 Some(inner),8459 Some(inner),
8346 )),8460 )),
8347 "store" => return Ok(Val::new(8461 "store" => return Ok(Val::new(
8348- format!("{}.store({}, {})", recv.code, a[0], a[1]),8462+ format!("rsAtomicStore({}, {}, {})", recv.code, a[0], a[1]),
8349 Some(Nim::Unit),8463 Some(Nim::Unit),
8350 )),8464 )),
8351 "swap" => "exchange",8465 "swap" => "exchange",
modified src/prelude_sync.nim +21 -0
@@ -97,3 +97,24 @@ proc rsNewJoinHandle*[T](): RsJoinHandle[T] =
9797 proc rsJoin*[T](h: RsJoinHandle[T]): T =
9898 joinThread(h.th)
9999 h.res[]
100+
101+## Rust's `#[derive(Debug)]` on an atomic prints the value it holds, read with
102+## a relaxed load. Nim's `load` wants a mutable location and a `Debug` does not
103+## have one, so the address is taken -- the read itself is unchanged.
104+template rsDebug*[T](a: Atomic[T]): string =
105+ rsDebug(rsAtomicLoad(a, moRelaxed))
106+
107+template rsDisplay*[T](a: Atomic[T]): string = rsDebug(a)
108+
109+## Rust's atomic operations take `&self`: reading and writing through a shared
110+## reference is the whole point of an atomic. Nim's `load`/`store` want a
111+## mutable location, so these take the address. The operation is unchanged --
112+## only the way Nim is told where it happens.
113+## Templates, not procs: a proc parameter is a copy, so `unsafeAddr` inside one
114+## takes the address of the copy and a store is written to it and lost. A
115+## template expands where it is written, so the address is the caller's.
116+template rsAtomicLoad*(a, order: untyped): untyped =
117+ cast[ptr typeof(a)](unsafeAddr a)[].load(order)
118+
119+template rsAtomicStore*(a, v, order: untyped): untyped =
120+ cast[ptr typeof(a)](unsafeAddr a)[].store(v, order)
@@ -97,3 +97,24 @@ proc rsNewJoinHandle*[T](): RsJoinHandle[T] =
97 proc rsJoin*[T](h: RsJoinHandle[T]): T =97 proc rsJoin*[T](h: RsJoinHandle[T]): T =
98 joinThread(h.th)98 joinThread(h.th)
99 h.res[]99 h.res[]
100+
101+## Rust's `#[derive(Debug)]` on an atomic prints the value it holds, read with
102+## a relaxed load. Nim's `load` wants a mutable location and a `Debug` does not
103+## have one, so the address is taken -- the read itself is unchanged.
104+template rsDebug*[T](a: Atomic[T]): string =
105+ rsDebug(rsAtomicLoad(a, moRelaxed))
106+
107+template rsDisplay*[T](a: Atomic[T]): string = rsDebug(a)
108+
109+## Rust's atomic operations take `&self`: reading and writing through a shared
110+## reference is the whole point of an atomic. Nim's `load`/`store` want a
111+## mutable location, so these take the address. The operation is unchanged --
112+## only the way Nim is told where it happens.
113+## Templates, not procs: a proc parameter is a copy, so `unsafeAddr` inside one
114+## takes the address of the copy and a store is written to it and lost. A
115+## template expands where it is written, so the address is the caller's.
116+template rsAtomicLoad*(a, order: untyped): untyped =
117+ cast[ptr typeof(a)](unsafeAddr a)[].load(order)
118+
119+template rsAtomicStore*(a, v, order: untyped): untyped =
120+ cast[ptr typeof(a)](unsafeAddr a)[].store(v, order)