doc/map.md
XGo provides a concise syntax for working with maps. Maps are key-value data structures that allow you to store and retrieve values using keys.
XGo provides two ways to create maps: using map literals for quick initialization with data, and using the make function for more control over map types and capacity.
In XGo, you can create maps using curly braces {}:
a := {"Hello": 1, "xsw": 3} // map[string]int
b := {"Hello": 1, "xsw": 3.4} // map[string]float64
c := {"Hello": 1, "xsw": "XGo"} // map[string]any
e := {1: "one", 2: "two"} // map[int]string
d := {} // map[string]any
When using the := syntax without explicit type declaration, XGo automatically infers the complete map type map[KeyType]ValueType based on the literal syntax and values provided.
Type Inference Rules
Both KeyType and ValueType follow the same inference rules:
any.{}: Inferred as map[string]any by default for maximum flexibility.You can also explicitly specify the map type to override automatic type inference:
var a map[string]float64 = {"Hello": 1, "xsw": 3} // Values converted to float64
var c map[string]any = {"x": 1, "y": "text"} // Explicit any type
When a type is explicitly declared, the literal values are converted to match the declared type.
makeUse make when you need an empty map or want to optimize performance by pre-allocating capacity.
make Syntax// Basic creation
m := make(map[string]int)
m["count"] = 42
echo m // Output: map[count:42]
// Create a map with complex key types
type Point (x, y int)
positions := make(map[Point]string)
positions[(0, 0)] = "origin"
For performance optimization, you can specify an initial capacity hint:
// Create a map with initial capacity for ~100 elements
// Pre-allocating capacity (helps performance for large maps)
largeMap := make(map[string]int, 100)
// This doesn't limit the map size, but helps reduce allocations
for i := 0; i < 150; i++ {
largeMap["key${i}"] = i
}
The capacity hint doesn't limit the map's size but helps the runtime allocate memory more efficiently when you know approximately how many elements you'll add.
make vs LiteralsUse map literals ({}) when:
Use map literals with explicit type (var m map[K]V = {}) when:
Use make when:
makeBefore manipulating maps, it is important to understand that XGo supports two notations for referencing keys:
m["key"]): The universal syntax. It works for all key types and allows using variables as keys.m.key): A convenient shorthand for string-keyed maps when the key is a valid identifier (no spaces or special characters).Field access is pure syntax sugar - m.field and m["field"] behave identically in all contexts.
Both notations are used for both assigning values and retrieving them.
To add a new key-value pair or update an existing one, assign a value to a key using either notation. If the key exists, its value is updated; otherwise, a new entry is created.
a := {"a": 1, "b": 0}
// Using bracket notation
a["c"] = 100
// Using field notation
a.d = 200
echo a // Output: map[a:1 b:0 c:100 d:200]
// Works with maps created by make too
m := make(map[string]int)
m["x"] = 10
m.y = 20
echo m // Output: map[x:10 y:20]
Use the delete function to remove elements from a map:
a := {"a": 1, "b": 0, "c": 100}
delete(a, "b")
echo a // Output: map[a:1 c:100]
// Works with any key type
m := make(map[int]string)
m[1] = "one"
m[2] = "two"
delete(m, 1)
echo m // Output: map[2:two]
You can get the number of elements in a map using the len function:
a := {"a": 1, "b": 2, "c": 3}
echo len(a) // Output: 3
b := make(map[string]int)
b["x"] = 10
echo len(b) // Output: 1
XGo provides flexible ways to retrieve map values, including safety checks for missing keys.
The traditional way to access map elements is using the [] operator with a key:
a := {"name": "Alice", "age": 25}
echo a["name"] // Output: Alice
// Works with any key type
m := make(map[int]string)
m[42] = "answer"
echo m[42] // Output: answer
For string-keyed maps, XGo allows you to use dot notation when the key is a valid identifier:
config := {"host": "localhost", "port": 8080}
echo config.host // Output: localhost
echo config.port // Output: 8080
// Equivalent to:
echo config["host"]
echo config["port"]
Use field notation (m.field) when:
Use bracket notation (m["key"]) when:
// Field notation - clean and readable
user := {"name": "Alice", "age": 30}
echo user.name
echo user.age
// Bracket notation - necessary for dynamic or special keys
keyName := "name"
echo user[keyName] // Dynamic key
echo user["first-name"] // Key with hyphen
echo user["2024-score"] // Key starting with digit
// Bracket notation - required for non-string keys
scores := make(map[int]float64)
scores[1] = 95.5
echo scores[1] // Must use bracket notation
Field notation works seamlessly with nested maps:
data := {
"user": {
"profile": {
"name": "Alice",
"age": 30,
},
},
}
// Clean nested access
echo data.user.profile.name // Output: Alice
// Equivalent to:
echo data["user"]["profile"]["name"]
any TypeEither notation also works with variables of type any, automatically treating them as map[string]any:
var response any = {"status": "ok", "code": 200}
echo response.status // Output: ok
echo response.code // Output: 200
echo response["status"] // Output: ok
echo response["code"] // Output: 200
When accessing uncertain data (such as from JSON or external APIs), use the comma-ok form to safely check if a path exists. The comma-ok form returns two values:
a := {"a": 1, "b": 0}
// Check if key exists
v, ok := a["c"]
echo v, ok // Output: 0 false (key doesn't exist)
v, ok = a["b"]
echo v, ok // Output: 0 true (key exists with value 0)
// Works with field notation too
v, ok = a.c
echo v, ok // Output: 0 false
// Direct conditional check
if v, ok := a["c"]; ok {
echo "Found:", v
} else {
echo "Not found" // Output: Not found
}
With comma-ok, accessing non-existent paths never panics - it simply returns false:
data := {"user": {"name": "Alice"}}
// Safe single-level access
name, ok := data.user
if ok {
echo "User found:", name
}
// Safe nested access
profile, ok := data.user.profile
if !ok {
echo "Profile not found" // This will print
}
// Safe access with type assertion
var response any = {"status": "ok", "code": 200}
code, ok := response.code.(int)
if ok {
echo "Status code:", code
}
This is especially useful when working with dynamic data:
var data any = {"user": "Alice"}
// Without comma-ok - may panic if structure is wrong
// name := data.user.profile.name // Would panic!
// With comma-ok - safe, never panics
name, ok := data.user.profile.name
if !ok {
echo "Path does not exist" // Output: Path does not exist
name = "Unknown"
}
// Processing API response
var apiResponse any = fetchFromAPI()
// Safely extract nested values
if userID, ok := apiResponse.data.user.id.(string); ok {
processUser(userID)
} else {
echo "Invalid response structure"
}
// With fallback values
city := "Unknown"
if c, ok := apiResponse.user.address.city.(string); ok {
city = c
}
echo "City:", city
XGo provides two forms of for in loop for iterating over maps:
m := {"x": 10, "y": 20, "z": 30}
for key, value in m {
echo key, value
}
// Works with any map type
ages := make(map[string]int)
ages["Alice"] = 30
ages["Bob"] = 25
for name, age in ages {
echo name, "is", age, "years old"
}
To iterate over just the keys, you can use the blank identifier _ for the value part.
m := {"x": 10, "y": 20, "z": 30}
for key, _ in m {
echo key
}
m := {"x": 10, "y": 20, "z": 30}
for value in m {
echo value
}
Map comprehensions provide a concise and expressive way to create new maps by transforming or filtering existing sequences. They follow a syntax similar to Python's dictionary comprehensions.
The general form of a map comprehension is:
{keyExpr: valueExpr for vars in iterable}
This creates a new map where each element from the iterable is transformed into a key-value pair.
// Map slice values to their indices
numbers := [10, 20, 30, 40, 50]
valueToIndex := {v: i for i, v in numbers}
echo valueToIndex // Output: map[10:0 20:1 30:2 40:3 50:4]
// Character positions in a string
word := "hello"
charPositions := {char: i for i, char in word}
echo charPositions // Output: map[h:0 e:1 l:3 o:4]
// Note: 'l' appears twice, so the last occurrence (index 3) is kept
Add an if clause to filter elements:
{keyExpr: valueExpr for vars in iterable if condition}
numbers := [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
// Only even numbers
evenSquares := {v: v * v for v in numbers if v%2 == 0}
echo evenSquares // Output: map[2:4 4:16 6:36 8:64 10:100]
// Only odd indices
oddIndexValues := {i: v for i, v in numbers if i%2 == 1}
echo oddIndexValues // Output: map[1:2 3:4 5:6 7:8 9:10]
// Using literals for initial configuration
config := {
"host": "localhost",
"port": 8080,
"debug": true,
}
// Access with field notation
echo "Connecting to", config.host, "on port", config.port
// Using explicit type for type safety
var settings map[string]int = {
"maxConnections": 100,
"timeout": 30,
}
// Using make for type-safe configuration
options := make(map[string]int)
options["maxConnections"] = 100
options["timeout"] = 30
var response any = parseJSON(apiData)
// Safe extraction with defaults
userID, ok := response.user.id.(string)
if !ok {
userID = "guest"
}
userName, ok := response.user.name.(string)
if !ok {
userName = "Anonymous"
}
echo "User:", userName, "(", userID, ")"
// Using make with pre-allocated capacity
wordCounts := make(map[string]int, 1000)
for word in words {
wordCounts[word]++
}
// Using comprehension to initialize
words := ["apple", "banana", "apple", "orange", "banana", "apple"]
uniqueWords := {w: 0 for w in words} // Initialize all to 0
for word in words {
uniqueWords[word]++
}
// Simple lookup table with literals
statusCodes := {
"ok": 200,
"not_found": 404,
"error": 500,
}
echo statusCodes.ok // Output: 200
// Using comprehension to reverse the mapping
codeToStatus := {code: status for status, code in statusCodes}
echo codeToStatus[200] // Output: ok
// Cache with pre-allocated capacity for performance
cache := make(map[string][]byte, 10000)
func getCachedData(key string) []byte {
if data, ok := cache[key]; ok {
return data
}
data := fetchData(key)
cache[key] = data
return data
}
// Group items by category
groups := make(map[string][]string)
for item in items {
category := getCategory(item)
groups[category] = append(groups[category], item)
}
// Access grouped data
for category, items in groups {
echo "Category:", category
for item in items {
echo " -", item
}
}
make when you need specific types, non-string keys, or want to pre-allocate capacitymake for large maps when the approximate size is knownmake with explicit types for better code documentation and type safety in larger projectsmake(map[K]V, size) to reduce allocations