Pre-release Harn is pre-1.0 — the language, standard library, and CLI may change between releases. See the release notes

Pick fields from a record

pick(source, keys) builds a new record from the fields you name. It's a global builtin, so you don't import it.

type Context = {env: HarnessEnv, fs: HarnessFs}

fn config_exists(ctx: Context) -> bool {
  const path = ctx.env.get_or("APP_CONFIG", "harn.toml")
  return ctx.fs.exists(path)
}

fn main(harness: Harness) {
  const ctx = pick(harness, ["env", "fs"])
  harness.stdio.println(config_exists(ctx))
}

ctx has the type {env: HarnessEnv, fs: HarnessFs}. Each handle keeps its grants, so config_exists can call methods on it.

Arguments#

ArgumentAccepts
sourceA record, a dictionary, a struct value, or the root Harness
keysA list of strings. Each string names one top-level field

What pick returns at runtime#

  • Every picked value is kept as-is, including nil, false, 0, and "".
  • A key the source doesn't have is skipped.
  • A repeated key produces one field.
  • An empty list returns {}.
  • Keys don't reach into nested records. "a.b" looks for a field named a.b.
  • The source isn't changed. Editing a nested record in the result leaves the source alone. Capability handles stay shared with the source.
  • Any other kind of source, or a key that isn't a string, is a runtime error.
fn main(harness: Harness) {
  const source = {name: "Ada", age: 37, absent: nil}
  const picked = pick(source, ["name", "absent", "name"])
  assert_eq(picked, {name: "Ada", absent: nil})
  assert_eq(pick(source, []), {})
}

What the checker knows about the result#

The result has the type of the fields you picked. What the checker can promise depends on how you write keys.

keysResult type
A list literal such as ["name", "age"]Those fields, with their original types. Optional fields stay optional
A const that holds a list literal, or an alias of that constThe same as the literal
A const string inside a list literalThe field that string names
A list only known at runtimeEvery field it could name, all optional

A runtime list can be empty, so the checker can't promise that any field is present. When the list has the type list<"name" | "age">, only those two fields appear in the result, and both are optional.

type Person = {name: string, age?: int}

type Partial = {name?: string, age?: int}

fn some_fields(person: Person, keys: list<string>) -> Partial {
  return pick(person, keys)
}

fn main(harness: Harness) {
  const fields = ["name"]
  const result: {name: string} = pick({name: "Ada", age: 37}, fields)
  assert_eq(result.name, "Ada")
}

The checker reports two mistakes. A literal key that the source type doesn't have:

pick: unknown field `fss` in `Harness`

And reading a field you didn't pick, such as ctx.tools after pick(harness, ["env", "fs"]):

field `tools` does not exist on shape `{fs: HarnessFs, env: HarnessEnv}`

Source types#

Source typeResult
A record such as {name: string, age?: int}The picked fields, with their types
A dictionary such as dict<string, int>Optional fields of type int, because the dictionary may not hold them
An open record such as {name: string, ...dict<string, int>}Declared fields keep their types. Other keys are optional int
A type alias or a generic structResolved first, then picked like a record
A union such as {kind: "text", value: string} | {kind: "number", value: int}Picked branch by branch, so kind and value stay linked. A literal key must exist in every branch
An intersection such as {name: string} & {age: int}The combined fields, then picked like a record

A function or a local value named pick shadows the builtin. Passing pick as a value instead of calling it gives the return type dict<string, unknown>.

Pipe form#

Use _ for the source:

fn main(harness: Harness) {
  const ctx = harness |> pick(_, ["env", "fs"])
  assert_eq(type_of(ctx.fs), type_of(harness.fs))
}

Automatic repairs#

The prefer-pick lint (HARN-LNT-077) recognizes copies of two or more fields from the same source when all fields are known to exist. harn lint --fix or harn fix --apply --code HARN-LNT-077 replaces those copies with pick. Optional fields and literals with comments stay unchanged.