返回资讯中心
外部精选
软件工程
#开发者实时流

Nim Version 2.4.0

copyMem(addr result[oldLen], buf, buflen)

Hacker News 最新WithinReason2026/10/29 · 今天21 分钟阅读

以下正文同步自 Hacker News 最新,版权归原站所有,已转换为易读排版。

before

result.setLen newLen copyMem(addr result[oldLen], buf, buflen)

after

copyMem(beginStore(result, newLen, oldLen), buf, buflen) endStore(result)

Reading bytes out of a string:

before

copyMem(dest, addr(s.data[s.pos]), n)

after

copyMem(dest, readRawData(s.data, s.pos), n)

Read into a slice of a string:

before

prepareMutation(buffer) # COW literal guard under ARC result = s.readData(addr buffer[slice.a], count)

after

result = s.readData(beginStore(buffer, slice.b + 1 - slice.a, slice.a), count) endStore(buffer)

cstring conversions

Converting string → cstring still works, but the C string is only valid as long as the source string is alive and unmodified.

If you need a stable, NUL-terminated buffer to hand to C, keep the owning string alive and derive the pointer from it (via readRawDataStable / the normal cstring conversion) rather than caching the pointer past a move.

Migration checklist

Grep your code for addr s[, unsafeAddr s[, addr(...data[, and any cached pointer/cstring into a string.

Replace read accesses with readRawData (or readRawDataStable if the pointer escapes the current scope / must survive a move).

Replace setLen + raw write with beginStore / endStore.

Drop now-redundant prepareMutation calls before indexed writes and beginStore.

Don’t assume addr s[0] is stable across add, reassignment, or passing the string by value.

Build once with --strings:sso and once without to confirm the code compiles and behaves identically under both.

Because the helper API has identical signatures on every string backend, code written this way keeps working under refc/ARC/ORC without SSO; you can port incrementally and flip --strings:sso on when ready.

Updating your code is not required, but beneficial. The new API works for all mm switches and is also offered by Nimony/Nim 3.

IC is finally shipping! Use it with the switch --ic:on. Parallel builds are based on multi-processing, not multi-threading. Hence macros that make use of global .compileTime variables might need to get patched before you can enjoy the benefits of much faster rebuilds.

For an example of the required porting effort, see my PR here that allows PolyWorld to be built with --ic:on!

Depending on the shape of your import graphs, even cold builds might be faster with IC turned on, as the build exploits more parallelism. For example, compiling the Nim compiler itself takes 31s with --ic:off on a machine with 16 physical cores, and 25s with --ic:on.

YRC (“ürk”) is the thread-safe variant of Nim’s ORC. Use --mm:yrc when you need shared refs and cycles across threads. It offers the same API as ORC. Where it fits:

ORC can only handle cycles that stay inside one thread. YRC lifts that: cycles can span threads. For acyclic data it uses atomic reference counting; for cyclic data it uses a write barrier that only runs at pointer assignments. There is no stop-the-world phase and no arbitrary safepoints. Any thread can run the cycle collector when needed (there is no dedicated GC thread), which helps locality and suits embedded systems where every thread counts.

Nothing is free. In our orcbench, YRC is about 2.0× slower than ORC and it uses twice as much memory, too. Still, we think that’s a good trade: you no longer have to worry about cycles crossing thread boundaries. The cost is a constant factor of two; the payoff grows with every core you put to work on shared data.

Nim always offered Ada-inspired “discriminated unions”, for example:

type NodeKind = enum # the different node types IntLit, # a leaf with an integer value FloatLit, # a leaf with a float value StringLit, # a leaf with a string value AddOp, # an addition SubOp, # a subtraction IfStmt # an if statement Node = ref object case kind: NodeKind of IntLit: intVal: int of FloatLit: floatVal: float of StringLit: strVal: string of AddOp, SubOp: leftOp, rightOp: Node of IfStmt: condition, thenPart, elsePart: Node

While this works, people complained about the lack of concise notation for years. So version 2.4 finally offers something better:

type Node = ref object case of IntLit: intVal: int of FloatLit: floatVal: float of StringLit: strVal: string of AddOp, SubOp: leftOp, rightOp: Node of IfStmt: condition, thenPart, elsePart: Node

There is no NodeKind enum anymore and no kind field: the branch names are introduced by the type declaration itself. Under the hood a sum type is still a case object with a hidden discriminator, so the memory layout and the performance are the same as before. (However, in the future the pattern matching allows for the runtime checks to be eliminated!)

A branch name constructs the object, with named arguments for the fields of its branch:

let n = AddOp(leftOp: IntLit(intVal: 40), rightOp: IntLit(intVal: 2))

Generic sum types work too. The instance is inferred from the field values or from the expected type:

type Opt[T] = object case of None: discard of Some: val: T

let a = Some(val: "abc") # Opt[string] let b: Opt[int] = None() # the expected type selects the instance

The fields can only be accessed via pattern matching, so accessing the field of the wrong branch, the classic FieldDefect, cannot happen anymore. The fields are bound to the names that follow the branch name:

proc eval(n: Node): int = case n of IntLit(v): v of AddOp(a, b): eval(a) + eval(b) of SubOp(a, b): eval(a) - eval(b) else: raiseAssert "not an arithmetic expression"

_ skips a field, {AddOp, SubOp}(a, b) matches several branches that share their fields, and the case has to cover all branches or have an else. If the matched expression is a mutable location, the bindings are views into the object and can be assigned to:

type Shape = object case of Circle: radius: float of Rect: w, h: float

var shapes = @[Circle(radius: 1.0), Rect(w: 1.0, h: 2.0)] for i in 0 ..< shapes.len: case shapes[i] of Circle(r): r = r * 2 # updates shapes[i].radius of Rect(w, _): w = 3.0

Otherwise the fields are bound by value. If you really need to access a field outside of a case, there is an escape hatch: {.cast(uncheckedAccess).}: echo n.intVal.

Classic case objects continue to work and are not deprecated.

This feature has to be enabled with --experimental:ownedRefs and I can promise you that we will break your code again if you use the feature.

owned ref T (and owned closures) mark the location that holds the unique owning edge to an object. This is checked statically: an owned location cannot be copied, only moved, and only fresh values like object constructions can initialize it. Any number of ordinary references to the same object can exist and keep it alive; the runtime representation is the same as that of ref T, so code with and without the feature interoperates.

{.experimental: "ownedRefs".} type Node = ref object next: owned Node data: int

The point of the exercise is cycle collection: owned edges form a forest, so a type whose references are all owned or .cursor can never be part of a cycle and stays out of the cycle collector under ORC and YRC. See the experimental manual for the details.

Nim always allowed modules to import each other, but in a cycle a module only saw the declarations of the other module that came before the import. In practice that meant juggling the order of imports and declarations, or moving all types into a types.nim module.

Import cycles are still a design smell as they turn the import DAG into a general graph, but sometimes they are the natural structure, and then the compiler should not stand in your way.

You can annotate the imports of a cycle with the cyclic pragma:

module a

import b {.cyclic.}

type A* = object b*: B # a type of b, even though b imports a

proc isEven*(n: int): bool = n == 0 or isOdd(n - 1)

module b

import a {.cyclic.}

type B* = object x*: int a*: ref A

proc isOdd*(n: int): bool = n != 0 and isEven(n - 1)

The modules connected by such imports form a cycle group that is checked phase by phase: first the imports of all modules, then all type sections, then the routines (their bodies come later), then the rest of each module. So types can refer to each other across modules, and the procs of the modules can call each other, regardless of the order of the declarations.

It is good practice to annotate the imports on both sides of a cycle. Import cycles without the annotation still work as before, but are deprecated; compile with --warning:ImplicitCyclicImport:on to find them. The precise rules are described in the manual.

正文由 探How 从来源站点 RSS 同步,内容未经改写;遇到排版缺失或需要图片、视频时请以原文为准。

打开原文
去社区讨论 →