+ "markdown": "---\ntitle: \"Using HashMaps\"\n---\n\n\n::: {.cell}\n\n:::\n\n\nIn addition to [vectors](./vectors.qmd) and [lists](./extendr-macro.qmd), extendr supports using Rust's `HashMap` type as function arguments. This allows you to work with R named lists using Rust's hash map data structure.\n\n::: callout-note\nA `HashMap` is Rust's implementation of a hash table. It stores key-value pairs and provides fast lookup, insertion, and deletion operations. Unlike R's named lists or vectors, HashMaps do not maintain any particular ordering of their elements.\n:::\n\n### Basic HashMap Type Mapping\n\nThe table below shows common HashMap types that can be used with extendr:\n\n| R type | Rust type | Description |\n|---------------------------------|------------------------------|--------------------------------------|\n| `list(a = 1L, b = 2L)` | `HashMap<String, i32>` | Named list with integer values |\n| `list(a = 1.0, b = 2.0)` | `HashMap<String, f64>` | Named list with double values |\n| `list(a = \"x\", b = \"y\")` | `HashMap<String, String>` | Named list with character values |\n| `list(a = TRUE, b = FALSE)` | `HashMap<String, bool>` | Named list with logical values |\n| `list(a = list(), b = 1)` | `HashMap<String, Robj>` | Named list with mixed types |\n\n::: callout-important\n## HashMap Ordering and Duplicate Names\n\nThere are two important behaviors to be aware of when using HashMaps with R lists:\n\n1. **Unordered**: HashMaps do not maintain insertion order. When you convert a HashMap back to an R list using `List::from_hashmap()`, the order of elements may differ from the original input.\n\n2. **Duplicate Names**: If an R list contains duplicate names (e.g., `list(x = 1, x = 2)`), only the last value will be retained in the HashMap. In this example, the HashMap would contain `(\"x\", 2)`.\n:::\n\n### Using HashMaps in Functions\n\nTo use a HashMap in your extendr functions, you need to import `std::collections::HashMap` and specify it as a function argument type. Here's a simple example that takes a named list of integers:\n\n\n::: {.cell}\n\n```{.rust .cell-code}\nuse std::collections::HashMap;\n\n#[extendr]\nfn test_hm_i32(mut x: HashMap<String, i32>) -> List {\n x.insert(\"inserted_value\".to_string(), 314);\n List::from_hashmap(x).unwrap()\n}\n```\n:::\n\n\nThis function accepts a named list of integers, adds a new key-value pair, and returns the modified list back to R using `List::from_hashmap()`.\n\n\n::: {.cell}\n\n```{.r .cell-code}\ntest_hm_i32(list(a = 1L, b = 2L, c = 3L))\n#> $inserted_value\n#> [1] 314\n#> \n#> $a\n#> [1] 1\n#> \n#> $c\n#> [1] 3\n#> \n#> $b\n#> [1] 2\n```\n:::\n\n\nNotice that the order of elements in the returned list may differ from the input order due to HashMap's unordered nature.\n\n### Working with Mixed Types\n\nWhen working with named lists that contain different types of values, use `HashMap<String, Robj>` to accept any R object:\n\n\n::: {.cell}\n\n```{.rust .cell-code}\nuse std::collections::HashMap;\n\n#[extendr]\nfn test_hm_string(mut x: HashMap<String, Robj>) -> List {\n x.insert(\"inserted_value\".to_string(), List::new(0).into());\n List::from_hashmap(x).unwrap()\n}\n```\n:::\n\n\n\n::: {.cell}\n\n```{.r .cell-code}\ntest_hm_string(\n list(\n a = 1L,\n b = \"hello\",\n c = c(1.0, 2.0, 3.0)\n )\n)\n#> $inserted_value\n#> list()\n#> \n#> $b\n#> [1] \"hello\"\n#> \n#> $a\n#> [1] 1\n#> \n#> $c\n#> [1] 1 2 3\n```\n:::\n\n\n### Custom Types with TryFrom\n\nSimilar to the examples in the [`serde` integration guide](../serde-integration.qmd), you can use HashMaps with custom types by implementing the `TryFrom<Robj>` trait. This is particularly useful when you want to work with complex data structures.\n\nHere's an example using a custom `Point` struct:\n\n\n::: {.cell}\n\n```{.rust .cell-code code-fold=\"true\" code-summary=\"View TryFrom trait implementations\"}\nuse std::collections::HashMap;\n\nstruct Point {\n x: f64,\n y: f64,\n}\n\nimpl TryFrom<Robj> for Point {\n type Error = Error;\n\n fn try_from(value: Robj) -> std::result::Result<Self, Self::Error> {\n let inner_vec = Doubles::try_from(value)?;\n let x = inner_vec[0].inner();\n let y = inner_vec[1].inner();\n Ok(Point { x, y })\n }\n}\n\nimpl From<Point> for Doubles {\n fn from(value: Point) -> Self {\n Doubles::from_values([value.x, value.y])\n }\n}\n\nimpl From<Point> for Robj {\n fn from(value: Point) -> Self {\n Robj::from(Doubles::from(value))\n }\n}\n```\n:::\n\n\n\n::: {.cell preamble='point_impl'}\n\n```{.rust .cell-code}\n#[extendr]\nfn test_hm_custom_try_from(mut x: HashMap<&str, Point>) -> List {\n x.insert(\"inserted_value\", Point { x: 3.0, y: 0.1415 });\n List::from_hashmap(x).unwrap()\n}\n```\n:::\n\n\nThis function accepts a named list where each element is a numeric vector of length 2, which gets converted to a `Point` struct:\n\n\n::: {.cell}\n\n```{.r .cell-code}\ntest_hm_custom_try_from(\n list(\n origin = c(0.0, 0.0),\n point_a = c(1.0, 2.0),\n point_b = c(3.0, 4.0)\n )\n)\n#> $origin\n#> [1] 0 0\n#> \n#> $point_b\n#> [1] 3 4\n#> \n#> $inserted_value\n#> [1] 3.0000 0.1415\n#> \n#> $point_a\n#> [1] 1 2\n```\n:::\n\n\n## See Also\n\n- [Vector Type Mapping](./vectors.qmd) - Learn about other collection types\n- [`serde` Integration](../serde-integration.qmd) - Working with custom structs and `TryFrom`\n- [Rust's HashMap documentation](https://doc.rust-lang.org/std/collections/struct.HashMap.html) - Official Rust documentation\n",
0 commit comments