Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
{
"hash": "debbd76f81be4fd4ef33aef4ed19d739",
"result": {
"engine": "knitr",
"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",
"supporting": [],
"filters": [
"rmarkdown/pagebreak.lua"
],
"includes": {},
"engineDependencies": {},
"preserve": {},
"postProcess": true
}
}
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
{
"hash": "dfeb026c860041c9bff785b1044b087d",
"hash": "fa4474a43b0362bc0b282c0a0adfc60e",
"result": {
"engine": "knitr",
"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:::",
"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",
"supporting": [],
"filters": [
"rmarkdown/pagebreak.lua"
Expand Down
5 changes: 5 additions & 0 deletions _quarto.yml
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
project:
type: website
render:
- "*.qmd"
- "*.md"
- "!CLAUDE.md"

execute:
freeze: true
Expand Down Expand Up @@ -70,6 +74,7 @@ website:
- user-guide/type-mapping/extendr-macro.qmd
- user-guide/type-mapping/scalars.qmd
- user-guide/type-mapping/vectors.qmd
- user-guide/type-mapping/collections.qmd
- user-guide/type-mapping/missing-values.qmd
- user-guide/type-mapping/characters.qmd
- section: "Error Handling"
Expand Down
16 changes: 7 additions & 9 deletions user-guide/type-mapping/vectors.qmd
Original file line number Diff line number Diff line change
Expand Up @@ -7,14 +7,12 @@ title: "Vector Type Mapping"
library(rextendr)
```

## Vector Type Mapping with Rust Types

What happens if we try to pass more than one value to `scalar_double()`?

```{extendrsrc, echo=FALSE}
#[extendr]
fn scalar_double(x: f64) {
rprintln!("The value of x is {x}");
fn scalar_double(x: f64) {
rprintln!("The value of x is {x}");
}
```

Expand Down Expand Up @@ -88,8 +86,8 @@ followed by a `;`.

```{extendrsrc}
#[extendr]
fn vector_double(x: Vec<f64>) -> Vec<f64> {
x
fn vector_double(x: Vec<f64>) -> Vec<f64> {
x
}
```

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

```{extendrsrc}
#[extendr]
fn vector_integer(x: Vec<i32>) -> Vec<i32> {
fn vector_integer(x: Vec<i32>) -> Vec<i32> {
x
}

#[extendr]
fn vector_character(x: Vec<String>) -> Vec<String> {
x
x
}
```

```{r}
vector_integer(c(4L, 6L, 8L))

vector_character(c("Hello world!", "Hello extendr!", "Hello R!"))
```
```