Skip to content

Commit 265a291

Browse files
authored
Merge pull request #70 from extendr/doc/hashmap
doc: add hashmap
2 parents 7d70d1f + 1cb34f1 commit 265a291

File tree

4 files changed

+29
-11
lines changed

4 files changed

+29
-11
lines changed
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
{
2+
"hash": "debbd76f81be4fd4ef33aef4ed19d739",
3+
"result": {
4+
"engine": "knitr",
5+
"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",
6+
"supporting": [],
7+
"filters": [
8+
"rmarkdown/pagebreak.lua"
9+
],
10+
"includes": {},
11+
"engineDependencies": {},
12+
"preserve": {},
13+
"postProcess": true
14+
}
15+
}

_freeze/user-guide/type-mapping/vectors/execute-results/html.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
{
2-
"hash": "dfeb026c860041c9bff785b1044b087d",
2+
"hash": "fa4474a43b0362bc0b282c0a0adfc60e",
33
"result": {
44
"engine": "knitr",
5-
"markdown": "---\ntitle: \"Vector Type Mapping\"\n---\n\n::: {.cell}\n\n:::\n\n\n## Vector Type Mapping with Rust Types\n\nWhat happens if we try to pass more than one value to `scalar_double()`?\n\n\n::: {.cell}\n\n:::\n\n::: {.cell}\n\n```{.r .cell-code}\nscalar_double(c(4.2, 1.3, 2.5))\n#> Error in scalar_double(c(4.2, 1.3, 2.5)): Expected Scalar, got Doubles\n```\n:::\n\n\nIt errors because the function expects a scalar of the `f64` type, not a vector\nof `f64`.\n\nIn this section, we show you how to pass Rust vectors between R and Rust.\n\n::: callout-important\nWhile using a Rust vector is possible in some cases, it is strongly not\nrecommended. Instead, extendr types should be used as they provide access\ndirectly to R objectes. Whereas using Rust vectors requires additional\nallocations.\n:::\n\nThe syntax is basically the same as with scalars, with just some minor changes.\nWe'll use doubles again to demonstrate this.\n\nFor reference, below are the type of Rust vectors that can be utilized with\nextendr.\n\n| R type | extendr type | Rust type |\n|---------------|--------------|---------------------|\n| `integer()` | `Integers` | `Vec<i32>` |\n| `double()` | `Doubles` | `Vec<f64>` |\n| `complex()` | `Complexes` | `Vec<Complex<f64>>` |\n| `character()` | `Strings` | `Vec<String>` |\n| `raw()` | `Raw` | `&[u8]` |\n| `logical()` | `Logicals` | |\n| `list()` | `List` | |\n\n::: callout-note\nYou might have anticipated `Vec<bool>` to be a supported Rust vector type. This\nis not possible because in R, logical vectors do not contain *only* `true` and\n`false` like Rust's bool type. They also can be an `NA` value which has no\ncorresponding representation in Rust.\n:::\n\nBelow defines Rust function which takes in a vector of `f64` values and prints\nthem out.\n\n\n::: {.cell}\n\n```{.rust .cell-code}\n#[extendr]\nfn vector_double(x: Vec<f64>) {\n rprintln!(\"The values of x are {x:?}\");\n}\n```\n:::\n\n\nThat function can be called from R which prints the Debug format of the vector.\n\n::: callout-tip\nRust's vector do not implement the\n[Display](https://doc.rust-lang.org/std/fmt/trait.Display.html) trait so the\ndebug format (`:?`) is used.\n:::\n\n\n::: {.cell}\n\n```{.r .cell-code}\nvector_double(c(4.2, 1.3, 2.5))\n#> The values of x are [4.2, 1.3, 2.5]\n```\n:::\n\n\nReturning values using Rust follows the same rules as R. You do not need to\nexplicitly return a value as long as the last item in an expression is not\nfollowed by a `;`.\n\n\n::: {.cell}\n\n```{.rust .cell-code}\n#[extendr]\nfn vector_double(x: Vec<f64>) -> Vec<f64> { \n x \n}\n```\n:::\n\n\nCalling the function returns the input as a double vector\n\n\n::: {.cell}\n\n```{.r .cell-code}\nx <- vector_double(c(4.2, 1.3, 2.5))\ntypeof(x)\n#> [1] \"double\"\nx + 1\n#> [1] 5.2 2.3 3.5\n```\n:::\n\n\n### Additional examples\n\nThese same principles can be extended to other supported vector types such as\n`Vec<i32>` and `Vec<String>`.\n\n\n::: {.cell}\n\n```{.rust .cell-code}\n#[extendr]\nfn vector_integer(x: Vec<i32>) -> Vec<i32> { \n x\n}\n\n#[extendr]\nfn vector_character(x: Vec<String>) -> Vec<String> {\n x \n}\n```\n:::\n\n::: {.cell}\n\n```{.r .cell-code}\nvector_integer(c(4L, 6L, 8L))\n#> [1] 4 6 8\n\nvector_character(c(\"Hello world!\", \"Hello extendr!\", \"Hello R!\"))\n#> [1] \"Hello world!\" \"Hello extendr!\" \"Hello R!\"\n```\n:::",
5+
"markdown": "---\ntitle: \"Vector Type Mapping\"\n---\n\n\n::: {.cell}\n\n:::\n\n\nWhat happens if we try to pass more than one value to `scalar_double()`?\n\n\n::: {.cell}\n\n:::\n\n\n\n::: {.cell}\n\n```{.r .cell-code}\nscalar_double(c(4.2, 1.3, 2.5))\n#> Error in scalar_double(c(4.2, 1.3, 2.5)): Expected Scalar, got Doubles\n```\n:::\n\n\nIt errors because the function expects a scalar of the `f64` type, not a vector\nof `f64`.\n\nIn this section, we show you how to pass Rust vectors between R and Rust.\n\n::: callout-important\nWhile using a Rust vector is possible in some cases, it is strongly not\nrecommended. Instead, extendr types should be used as they provide access\ndirectly to R objectes. Whereas using Rust vectors requires additional\nallocations.\n:::\n\nThe syntax is basically the same as with scalars, with just some minor changes.\nWe'll use doubles again to demonstrate this.\n\nFor reference, below are the type of Rust vectors that can be utilized with\nextendr.\n\n| R type | extendr type | Rust type |\n|---------------|--------------|---------------------|\n| `integer()` | `Integers` | `Vec<i32>` |\n| `double()` | `Doubles` | `Vec<f64>` |\n| `complex()` | `Complexes` | `Vec<Complex<f64>>` |\n| `character()` | `Strings` | `Vec<String>` |\n| `raw()` | `Raw` | `&[u8]` |\n| `logical()` | `Logicals` | |\n| `list()` | `List` | |\n\n::: callout-note\nYou might have anticipated `Vec<bool>` to be a supported Rust vector type. This\nis not possible because in R, logical vectors do not contain *only* `true` and\n`false` like Rust's bool type. They also can be an `NA` value which has no\ncorresponding representation in Rust.\n:::\n\nBelow defines Rust function which takes in a vector of `f64` values and prints\nthem out.\n\n\n::: {.cell}\n\n```{.rust .cell-code}\n#[extendr]\nfn vector_double(x: Vec<f64>) {\n rprintln!(\"The values of x are {x:?}\");\n}\n```\n:::\n\n\nThat function can be called from R which prints the Debug format of the vector.\n\n::: callout-tip\nRust's vector do not implement the\n[Display](https://doc.rust-lang.org/std/fmt/trait.Display.html) trait so the\ndebug format (`:?`) is used.\n:::\n\n\n::: {.cell}\n\n```{.r .cell-code}\nvector_double(c(4.2, 1.3, 2.5))\n#> The values of x are [4.2, 1.3, 2.5]\n```\n:::\n\n\nReturning values using Rust follows the same rules as R. You do not need to\nexplicitly return a value as long as the last item in an expression is not\nfollowed by a `;`.\n\n\n::: {.cell}\n\n```{.rust .cell-code}\n#[extendr]\nfn vector_double(x: Vec<f64>) -> Vec<f64> {\n x\n}\n```\n:::\n\n\nCalling the function returns the input as a double vector\n\n\n::: {.cell}\n\n```{.r .cell-code}\nx <- vector_double(c(4.2, 1.3, 2.5))\ntypeof(x)\n#> [1] \"double\"\nx + 1\n#> [1] 5.2 2.3 3.5\n```\n:::\n\n\n### Additional examples\n\nThese same principles can be extended to other supported vector types such as\n`Vec<i32>` and `Vec<String>`.\n\n\n::: {.cell}\n\n```{.rust .cell-code}\n#[extendr]\nfn vector_integer(x: Vec<i32>) -> Vec<i32> {\n x\n}\n\n#[extendr]\nfn vector_character(x: Vec<String>) -> Vec<String> {\n x\n}\n```\n:::\n\n\n\n::: {.cell}\n\n```{.r .cell-code}\nvector_integer(c(4L, 6L, 8L))\n#> [1] 4 6 8\n\nvector_character(c(\"Hello world!\", \"Hello extendr!\", \"Hello R!\"))\n#> [1] \"Hello world!\" \"Hello extendr!\" \"Hello R!\"\n```\n:::\n\n",
66
"supporting": [],
77
"filters": [
88
"rmarkdown/pagebreak.lua"

_quarto.yml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,9 @@
11
project:
22
type: website
3+
render:
4+
- "*.qmd"
5+
- "*.md"
6+
- "!CLAUDE.md"
37

48
execute:
59
freeze: true
@@ -70,6 +74,7 @@ website:
7074
- user-guide/type-mapping/extendr-macro.qmd
7175
- user-guide/type-mapping/scalars.qmd
7276
- user-guide/type-mapping/vectors.qmd
77+
- user-guide/type-mapping/collections.qmd
7378
- user-guide/type-mapping/missing-values.qmd
7479
- user-guide/type-mapping/characters.qmd
7580
- section: "Error Handling"

user-guide/type-mapping/vectors.qmd

Lines changed: 7 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -7,14 +7,12 @@ title: "Vector Type Mapping"
77
library(rextendr)
88
```
99

10-
## Vector Type Mapping with Rust Types
11-
1210
What happens if we try to pass more than one value to `scalar_double()`?
1311

1412
```{extendrsrc, echo=FALSE}
1513
#[extendr]
16-
fn scalar_double(x: f64) {
17-
rprintln!("The value of x is {x}");
14+
fn scalar_double(x: f64) {
15+
rprintln!("The value of x is {x}");
1816
}
1917
```
2018

@@ -88,8 +86,8 @@ followed by a `;`.
8886

8987
```{extendrsrc}
9088
#[extendr]
91-
fn vector_double(x: Vec<f64>) -> Vec<f64> {
92-
x
89+
fn vector_double(x: Vec<f64>) -> Vec<f64> {
90+
x
9391
}
9492
```
9593

@@ -108,18 +106,18 @@ These same principles can be extended to other supported vector types such as
108106

109107
```{extendrsrc}
110108
#[extendr]
111-
fn vector_integer(x: Vec<i32>) -> Vec<i32> {
109+
fn vector_integer(x: Vec<i32>) -> Vec<i32> {
112110
x
113111
}
114112
115113
#[extendr]
116114
fn vector_character(x: Vec<String>) -> Vec<String> {
117-
x
115+
x
118116
}
119117
```
120118

121119
```{r}
122120
vector_integer(c(4L, 6L, 8L))
123121
124122
vector_character(c("Hello world!", "Hello extendr!", "Hello R!"))
125-
```
123+
```

0 commit comments

Comments
 (0)