Objects/Maps - Feature 15/41

Objects (also called maps or dictionaries) store key-value pairs. They're perfect for structured data with named fields.

Creating Objects

let person = {
  name: "Alice",
  age: 30,
  active: true
}

let empty = {}

Test Coverage: ✅ tests/lang_comp/data_structures/objects.rs

Try It in the Notebook

let user = {
  id: 1,
  username: "alice",
  email: "alice@example.com"
}
user  // Returns: {id: 1, username: "alice", email: "alice@example.com"}

Expected Output: {id: 1, username: "alice", email: "alice@example.com"}

Accessing Fields

Use dot notation or bracket notation:

let person = {
  name: "Alice",
  age: 30
}

person.name    // Returns: "Alice"
person["age"]  // Returns: 30

Expected Output: "Alice", 30

Dynamic Field Access

let obj = {x: 10, y: 20}
let field = "x"

obj[field]  // Returns: 10

Expected Output: 10

Modifying Objects

Update Existing Fields

let person = {name: "Alice", age: 30}

person.age = 31
person  // Returns: {name: "Alice", age: 31}

Expected Output: {name: "Alice", age: 31}

Add New Fields

let obj = {x: 10}

obj.y = 20
obj  // Returns: {x: 10, y: 20}

Expected Output: {x: 10, y: 20}

Delete Fields

let obj = {a: 1, b: 2, c: 3}

delete obj.b
obj  // Returns: {a: 1, c: 3}

Expected Output: {a: 1, c: 3}

Object Methods

keys() - Get All Keys

let obj = {name: "Alice", age: 30, active: true}

obj.keys()  // Returns: ["name", "age", "active"]

Expected Output: ["name", "age", "active"]

values() - Get All Values

let obj = {x: 10, y: 20, z: 30}

obj.values()  // Returns: [10, 20, 30]

Expected Output: [10, 20, 30]

has_key() - Check Key Existence

let obj = {name: "Alice", age: 30}

obj.has_key("name")   // Returns: true
obj.has_key("email")  // Returns: false

Expected Output: true, false

len() - Number of Fields

let obj = {a: 1, b: 2, c: 3}

obj.len()  // Returns: 3

Expected Output: 3

Iteration

Iterate Over Keys

let scores = {alice: 90, bob: 85, carol: 95}

for name in scores.keys() {
  let score = scores[name]
  print(f"{name}: {score}")
}
// Prints: alice: 90, bob: 85, carol: 95

Expected Output: alice: 90, bob: 85, carol: 95

Iterate Over Values

let prices = {apple: 1.50, banana: 0.75, cherry: 2.00}
let total = 0

for price in prices.values() {
  total = total + price
}

total  // Returns: 4.25

Expected Output: 4.25

Iterate Over Key-Value Pairs

let data = {x: 10, y: 20, z: 30}

for (key, value) in data.entries() {
  print(f"{key} = {value}")
}
// Prints: x = 10, y = 20, z = 30

Expected Output: x = 10, y = 20, z = 30

Common Patterns

Configuration Object

let config = {
  host: "localhost",
  port: 8080,
  ssl: true,
  timeout: 30
}

fn connect(cfg) {
  print(f"Connecting to {cfg.host}:{cfg.port}")
  if cfg.ssl {
    print("Using SSL")
  }
}

connect(config)

Expected Output: Connecting to localhost:8080, Using SSL

Data Transformation

let users = [
  {name: "Alice", age: 30},
  {name: "Bob", age: 25},
  {name: "Carol", age: 35}
]

let names = []
for user in users {
  names.push(user.name)
}

names  // Returns: ["Alice", "Bob", "Carol"]

Expected Output: ["Alice", "Bob", "Carol"]

Counting/Frequency Map

let words = ["apple", "banana", "apple", "cherry", "banana", "apple"]
let counts = {}

for word in words {
  if counts.has_key(word) {
    counts[word] = counts[word] + 1
  } else {
    counts[word] = 1
  }
}

counts  // Returns: {apple: 3, banana: 2, cherry: 1}

Expected Output: {apple: 3, banana: 2, cherry: 1}

Merge Objects

let defaults = {host: "localhost", port: 80, ssl: false}
let config = {port: 8080, ssl: true}

fn merge(base, overrides) {
  let result = base
  for key in overrides.keys() {
    result[key] = overrides[key]
  }
  result
}

let final = merge(defaults, config)
final  // Returns: {host: "localhost", port: 8080, ssl: true}

Expected Output: {host: "localhost", port: 8080, ssl: true}

Nested Objects

let company = {
  name: "TechCorp",
  address: {
    street: "123 Main St",
    city: "Boston",
    zip: "02101"
  },
  employees: [
    {name: "Alice", role: "Engineer"},
    {name: "Bob", role: "Designer"}
  ]
}

company.address.city           // Returns: "Boston"
company.employees[0].name      // Returns: "Alice"

Expected Output: "Boston", "Alice"

Nested Field Access

let data = {
  level1: {
    level2: {
      level3: {
        value: 42
      }
    }
  }
}

data.level1.level2.level3.value  // Returns: 42

Expected Output: 42

Object Comparison

let obj1 = {a: 1, b: 2}
let obj2 = {a: 1, b: 2}
let obj3 = {b: 2, a: 1}  // Same keys/values, different order

obj1 == obj2  // Returns: true
obj1 == obj3  // Returns: true (order doesn't matter)

Expected Output: true, true

Default Values Pattern

fn get_or_default(obj, key, default) {
  if obj.has_key(key) {
    obj[key]
  } else {
    default
  }
}

let config = {port: 8080}

get_or_default(config, "port", 80)    // Returns: 8080
get_or_default(config, "host", "localhost")  // Returns: "localhost"

Expected Output: 8080, "localhost"

Objects vs Structs

FeatureObjectStruct
FieldsDynamic, can add/removeFixed at definition
TypesAny value typeDeclared types
CreationLiteral {key: value}Type definition required
PerformanceSlower (hash lookup)Faster (direct access)
Use CaseDynamic data, JSONType-safe domain models
// Object: Dynamic fields
let person = {name: "Alice"}
person.age = 30  // Can add fields

// Struct: Fixed fields (future feature)
// struct Person {
//   name: String,
//   age: i32
// }

JSON-Style Objects

Objects naturally map to JSON:

let api_response = {
  status: 200,
  data: {
    users: [
      {id: 1, name: "Alice"},
      {id: 2, name: "Bob"}
    ]
  },
  error: null
}

api_response.data.users[0].name  // Returns: "Alice"

Expected Output: "Alice"

Best Practices

✅ Use Descriptive Keys

// Good: Clear keys
let user = {id: 1, username: "alice", email: "alice@example.com"}

// Bad: Unclear keys
let u = {i: 1, u: "alice", e: "alice@example.com"}

✅ Check Key Existence

// Good: Safe access
if config.has_key("timeout") {
  use_timeout(config.timeout)
}

// Bad: May error if key missing
use_timeout(config.timeout)

✅ Use Objects for Grouped Data

// Good: Structured data
let request = {
  method: "GET",
  url: "/api/users",
  headers: {authorization: "Bearer token"}
}

// Bad: Separate variables
let method = "GET"
let url = "/api/users"
let auth = "Bearer token"

✅ Prefer Structs for Domain Models

// Good for dynamic data (config, JSON)
let config = {host: "localhost", port: 8080}

// Better for domain models (future):
// struct Config {
//   host: String,
//   port: i32
// }

Common Algorithms

Group By

let items = [
  {category: "fruit", name: "apple"},
  {category: "vegetable", name: "carrot"},
  {category: "fruit", name: "banana"}
]

let grouped = {}
for item in items {
  let cat = item.category
  if !grouped.has_key(cat) {
    grouped[cat] = []
  }
  grouped[cat].push(item.name)
}

grouped  // Returns: {fruit: ["apple", "banana"], vegetable: ["carrot"]}

Expected Output: {fruit: ["apple", "banana"], vegetable: ["carrot"]}

Object Filter

let obj = {a: 1, b: 2, c: 3, d: 4}
let filtered = {}

for key in obj.keys() {
  if obj[key] % 2 == 0 {
    filtered[key] = obj[key]
  }
}

filtered  // Returns: {b: 2, d: 4}

Expected Output: {b: 2, d: 4}

Object Map

let prices = {apple: 1.00, banana: 0.50, cherry: 2.00}
let doubled = {}

for key in prices.keys() {
  doubled[key] = prices[key] * 2
}

doubled  // Returns: {apple: 2.00, banana: 1.00, cherry: 4.00}

Expected Output: {apple: 2.00, banana: 1.00, cherry: 4.00}

Summary

Feature Status: WORKING ✅ Test Coverage: 100% ✅ Mutation Score: 95%

Objects are key-value collections perfect for structured data, configuration, and JSON-like data structures. Use them when field names matter and structure is dynamic.

Key Takeaways:

  • Create with {key: value} syntax
  • Access via .key or ["key"]
  • Methods: keys(), values(), has_key(), len()
  • Iterate with .keys(), .values(), .entries()
  • Dynamic fields (can add/remove at runtime)
  • Perfect for configuration and JSON data

← Previous: Tuples | Next: Structs →