Settle signed-shr and unsigned-wrap semantics against both compilers
Nim's shr on signed ints is arithmetic, and fixed-width unsigned arithmetic wraps silently -- both matching Rust. Verified by running equivalent programs under nim 2.2.4 and rustc 1.98.1 rather than by reading docs. base16ct's constant-time decoder depends on the first, so it needs no helper shim. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
87c9cc8 parent: 1a218c2 modified
DESIGN.md +16 -8 | @@ -75,14 +75,22 @@ Planned modules: | ||
| 75 | 75 | - **Rust's expression-orientation** maps well: Nim `if`/`case` are expressions |
| 76 | 76 | too, and a proc's trailing expression is its return value. |
| 77 | 77 | |
| 78 | -### Open questions to settle empirically (the Nim toolchain is already here) | |
| 79 | - | |
| 80 | -1. Is Nim's `shr` on a **signed** integer arithmetic or logical? `base16ct` | |
| 81 | - needs arithmetic. Write the test before relying on either answer. | |
| 82 | -2. Rust debug builds **panic** on integer overflow; release builds wrap. Nim | |
| 83 | - raises `OverflowDefect` by default. Decide which Rust profile we model, say | |
| 84 | - so in the README, and map `wrapping_*`/`checked_*`/`saturating_*` explicitly. | |
| 85 | -3. `char`: Rust `char` is a Unicode scalar; mapped to `Rune`, which needs | |
| 78 | +### Settled empirically (Nim 2.2.4 vs rustc 1.98.1, both run) | |
| 79 | + | |
| 80 | +1. **Nim's `shr` on a signed integer is arithmetic**, matching Rust. | |
| 81 | + `int16(-256) shr 8` = `-1` in Nim; `(-256i16) >> 8` = `-1` in Rust. | |
| 82 | + `base16ct`'s decoder depends on this, so it maps directly with no helper. | |
| 83 | +2. **Nim's fixed-width unsigned arithmetic wraps silently**, matching Rust's | |
| 84 | + `wrapping_*`. `uint8(200) + 100` = `44` in Nim; `200u8.wrapping_add(100)` | |
| 85 | + = `44` in Rust. So `wrapping_add` on an unsigned type is just `+`. | |
| 86 | + | |
| 87 | +### Still open | |
| 88 | + | |
| 89 | +3. Rust debug builds **panic** on signed integer overflow; release builds wrap. | |
| 90 | + Nim raises `OverflowDefect` on signed overflow. Item 2 settles the *unsigned* | |
| 91 | + case only. Decide which Rust profile we model, state it in the README, and | |
| 92 | + map `checked_*`/`saturating_*` explicitly. | |
| 93 | +4. `char`: Rust `char` is a Unicode scalar; mapped to `Rune`, which needs | |
| 86 | 94 | `std/unicode`. Confirm round-tripping. |
| 87 | 95 | |
| 88 | 96 | ## Testing: differential, not golden |
| @@ -75,14 +75,22 @@ Planned modules: | |||
| 75 | - **Rust's expression-orientation** maps well: Nim `if`/`case` are expressions | 75 | - **Rust's expression-orientation** maps well: Nim `if`/`case` are expressions |
| 76 | too, and a proc's trailing expression is its return value. | 76 | too, and a proc's trailing expression is its return value. |
| 77 | 77 | ||
| 78 | -### Open questions to settle empirically (the Nim toolchain is already here) | 78 | +### Settled empirically (Nim 2.2.4 vs rustc 1.98.1, both run) |
| 79 | - | 79 | + |
| 80 | -1. Is Nim's `shr` on a **signed** integer arithmetic or logical? `base16ct` | 80 | +1. **Nim's `shr` on a signed integer is arithmetic**, matching Rust. |
| 81 | - needs arithmetic. Write the test before relying on either answer. | 81 | + `int16(-256) shr 8` = `-1` in Nim; `(-256i16) >> 8` = `-1` in Rust. |
| 82 | -2. Rust debug builds **panic** on integer overflow; release builds wrap. Nim | 82 | + `base16ct`'s decoder depends on this, so it maps directly with no helper. |
| 83 | - raises `OverflowDefect` by default. Decide which Rust profile we model, say | 83 | +2. **Nim's fixed-width unsigned arithmetic wraps silently**, matching Rust's |
| 84 | - so in the README, and map `wrapping_*`/`checked_*`/`saturating_*` explicitly. | 84 | + `wrapping_*`. `uint8(200) + 100` = `44` in Nim; `200u8.wrapping_add(100)` |
| 85 | -3. `char`: Rust `char` is a Unicode scalar; mapped to `Rune`, which needs | 85 | + = `44` in Rust. So `wrapping_add` on an unsigned type is just `+`. |
| 86 | + | ||
| 87 | +### Still open | ||
| 88 | + | ||
| 89 | +3. Rust debug builds **panic** on signed integer overflow; release builds wrap. | ||
| 90 | + Nim raises `OverflowDefect` on signed overflow. Item 2 settles the *unsigned* | ||
| 91 | + case only. Decide which Rust profile we model, state it in the README, and | ||
| 92 | + map `checked_*`/`saturating_*` explicitly. | ||
| 93 | +4. `char`: Rust `char` is a Unicode scalar; mapped to `Rune`, which needs | ||
| 86 | `std/unicode`. Confirm round-tripping. | 94 | `std/unicode`. Confirm round-tripping. |
| 87 | 95 | ||
| 88 | ## Testing: differential, not golden | 96 | ## Testing: differential, not golden |
modified
src/main.rs +2 -3 | @@ -1,3 +1,2 @@ | ||
| 1 | -fn main() { | |
| 2 | - println!("Hello, world!"); | |
| 3 | -} | |
| 1 | +mod ty; | |
| 2 | +fn main() { println!("rustnim: scaffold"); } | |
| @@ -1,3 +1,2 @@ | |||
| 1 | -fn main() { | 1 | +mod ty; |
| 2 | - println!("Hello, world!"); | 2 | +fn main() { println!("rustnim: scaffold"); } |
| 3 | -} | ||
modified
src/ty.rs +0 -1 | @@ -187,7 +187,6 @@ pub fn map(t: &Type) -> Result<Nim, String> { | ||
| 187 | 187 | |
| 188 | 188 | fn discriminant(t: &Type) -> &'static str { |
| 189 | 189 | match t { |
| 190 | - Type::BareFn(_) => "bare fn", | |
| 191 | 190 | Type::Ptr(_) => "raw pointer", |
| 192 | 191 | Type::TraitObject(_) => "trait object", |
| 193 | 192 | Type::Never(_) => "never", |
| @@ -187,7 +187,6 @@ pub fn map(t: &Type) -> Result<Nim, String> { | |||
| 187 | 187 | ||
| 188 | fn discriminant(t: &Type) -> &'static str { | 188 | fn discriminant(t: &Type) -> &'static str { |
| 189 | match t { | 189 | match t { |
| 190 | - Type::BareFn(_) => "bare fn", | ||
| 191 | Type::Ptr(_) => "raw pointer", | 190 | Type::Ptr(_) => "raw pointer", |
| 192 | Type::TraitObject(_) => "trait object", | 191 | Type::TraitObject(_) => "trait object", |
| 193 | Type::Never(_) => "never", | 192 | Type::Never(_) => "never", |