## rustnim prelude — emitted at the top of every generated module. ## ## Everything here exists to make Nim's observable behaviour match Rust's ## exactly. Where the two languages already agree (signed `shr` is arithmetic ## in both; fixed-width unsigned arithmetic wraps in both; integer `div`/`mod` ## truncate toward zero in both) there is deliberately nothing here: the ## operator is mapped directly and no helper is involved. # Rust's `&[T]` is a borrowed view, not a copy. Nim's view types model exactly # that, including returning one from a proc: writing through the returned view # is visible in the original buffer. Probed against Nim 2.2.4 before relying on # it, because copying instead would silently change aliasing. {.experimental: "views".} import std/[unicode, strutils] type RustPanic* = object of CatchableError ## `core::fmt`'s own error and sink, kept distinct from any user type that ## happens to be called `Error`. A formatting impl is lowered to a proc that ## returns the formatted string, so these appear only in signatures. FmtError* = object FmtResult* = object ok*: bool Option*[T] = object case has*: bool of true: val*: T of false: discard Result*[T, E] = object case ok*: bool of true: val*: T of false: err*: E proc rsPanic*(msg: string) {.noreturn.} = raise newException(RustPanic, msg) proc rsSome*[T](v: T): Option[T] = Option[T](has: true, val: v) proc rsNone*[T](): Option[T] = Option[T](has: false) proc rsOk*[T, E](v: T): Result[T, E] = Result[T, E](ok: true, val: v) proc rsErr*[T, E](e: E): Result[T, E] = Result[T, E](ok: false, err: e) proc rsOkOr*[T, E](o: Option[T], e: E): Result[T, E] = if o.has: Result[T, E](ok: true, val: o.val) else: Result[T, E](ok: false, err: e) proc unwrapOr*[T](o: Option[T], d: T): T = if o.has: o.val else: d proc unwrapOr*[T, E](r: Result[T, E], d: T): T = if r.ok: r.val else: d proc unwrap*[T](o: Option[T]): T = if not o.has: rsPanic("called `Option::unwrap()` on a `None` value") o.val proc unwrap*[T, E](r: Result[T, E]): T = if not r.ok: rsPanic("called `Result::unwrap()` on an `Err` value") r.val # --------------------------------------------------------------------------- # Display / Debug. # # Rust's `{}` and `{:?}` are two distinct formats and they differ for floats, # strings, chars and sequences. Nim's `$` matches neither consistently, so both # are implemented here rather than approximated with `$`. # --------------------------------------------------------------------------- proc rsDisplay*(x: SomeInteger): string = $x proc rsDebug*(x: SomeInteger): string = $x proc rsDisplay*(x: bool): string = $x proc rsDebug*(x: bool): string = $x proc rsFloatStr(x: float64, debug: bool): string = ## Nim and Rust both print the shortest round-tripping decimal, but they ## spell the result differently in three places. if x != x: return "NaN" if x == Inf: return "inf" if x == -Inf: return "-inf" result = $x result = result.replace("e+", "e") # Nim `1e+21`, Rust `1e21` if not debug and result.endsWith(".0"): # Rust Display drops a bare `.0` result.setLen(result.len - 2) proc rsDisplay*(x: float32 | float64): string = rsFloatStr(float64(x), false) proc rsDebug*(x: float32 | float64): string = rsFloatStr(float64(x), true) proc rsDisplay*(x: string): string = x proc rsDebug*(x: string): string = result = "\"" for c in x: case c of '"': result.add("\\\"") of '\\': result.add("\\\\") of '\n': result.add("\\n") of '\t': result.add("\\t") of '\r': result.add("\\r") else: result.add(c) result.add("\"") proc rsDisplay*(x: Rune): string = $x proc rsDebug*(x: Rune): string = case $x of "'": "'\\''" of "\\": "'\\\\'" of "\n": "'\\n'" of "\t": "'\\t'" of "\r": "'\\r'" else: "'" & $x & "'" proc rsDebug*[T](x: seq[T] | openArray[T]): string = result = "[" for i in 0 ..< x.len: if i > 0: result.add(", ") result.add(rsDebug(x[i])) result.add("]") proc rsDebug*[T](o: Option[T]): string = if o.has: "Some(" & rsDebug(o.val) & ")" else: "None" proc rsDebug*[T, E](r: Result[T, E]): string = if r.ok: "Ok(" & rsDebug(r.val) & ")" else: "Err(" & rsDebug(r.err) & ")" # --------------------------------------------------------------------------- # Radix formats: `{:x}`, `{:X}`, `{:b}`, `{:o}`, with Rust's width/zero-fill. # Rust formats the *two's-complement bit pattern*, so a negative i8 prints as # `ff`, not `-1`. `toHex` on the unsigned view of the same width reproduces it. # --------------------------------------------------------------------------- proc rsRadix*[T: SomeInteger](x: T, base: int, upper: bool): string = var v: uint64 = when T is SomeSignedInt: # Sign-extend then mask to the type's own width, so the printed bit # pattern is the Rust one for this exact integer type. cast[uint64](int64(x)) and (if sizeof(T) == 8: high(uint64) else: (1'u64 shl (sizeof(T) * 8)) - 1) else: uint64(x) if v == 0: return "0" const digits = "0123456789abcdef" while v > 0'u64: result.add(digits[int(v mod uint64(base))]) v = v div uint64(base) for i in 0 ..< result.len div 2: swap(result[i], result[result.len - 1 - i]) if upper: result = result.toUpperAscii() proc rsPad*(s: string, width: int, zero: bool): string = if s.len >= width: return s let fill = width - s.len if zero and s.len > 0 and s[0] == '-': "-" & repeat('0', fill) & s[1 .. ^1] elif zero: repeat('0', fill) & s else: repeat(' ', fill) & s proc rsBytes*(s: string): seq[uint8] = ## Rust's `str::as_bytes` is a view of the UTF-8 encoding; Nim's `string` is ## already those bytes, so this is a reinterpretation, not a conversion. result = newSeq[uint8](s.len) for i in 0 ..< s.len: result[i] = uint8(s[i]) proc newSeqWith*[T](n: int, v: T): seq[T] = result = newSeq[T](n) for i in 0 ..< n: result[i] = v