diff --git a/_freeze/user-guide/basic-type-mapping/execute-results/html.json b/_freeze/user-guide/basic-type-mapping/execute-results/html.json new file mode 100644 index 0000000..c2ae229 --- /dev/null +++ b/_freeze/user-guide/basic-type-mapping/execute-results/html.json @@ -0,0 +1,15 @@ +{ + "hash": "6fc4dcad2ff549e86a05821eb9275471", + "result": { + "engine": "knitr", + "markdown": "---\ntitle: \"Scalar Type Mapping\"\n---\n\n::: {.cell}\n\n:::\n\n\nThis tutorial demonstrates some of the basics of passing data types back and\nforth between Rust and R. This includes all of the following:\n\n- Passing scalar types between R and Rust.\n- Passing vector types between R and Rust.\n- Printing from Rust to the console in R.\n- Handling missing values in Rust (a primer).\n\nWe'll start with examples showing how to pass R types as explicit Rust types.\nThis is useful for demonstration purposes, but it does ignore one very very big\nissue, and that's missing values. Rust data types do not allow for missing\nvalues, so they have to be handled carefully. Fortunately, extendr offers its\nown data types built on top of the Rust types to do that for you. For this\nreason, **it is strongly recommended that you work with the extendr types\nwherever possible.** However, when first getting comfortable with extendr, \nand possible even Rust, it may feel more comfortable to work with Rust\nnative types. \n\n## Scalar Type Mapping with Rust Types\n\nIn R, there is no such thing as a scalar value. Everything is a vector.\nWhen using a scalar value in R, that is really a length one vector. In Rust,\nhowever, scalar values are the building blocks of everything. \n\nBelow is a mapping of scalar values between R, extendr, and Rust. \n\n\n| R type | extendr type | Rust type |\n|----------------|--------------|----------------|\n| `integer(1)` | `Rint` | `i32` |\n| `double(1)` | `Rfloat` | `f64` |\n| `logical(1)` | `Rbool` | `bool` |\n| `complex(1)` | `Rcplx` | `Complex` |\n| `character(1)` | `Rstr` | `String` |\n\nTo see how these scalars get passed back and forth between Rust and R,\nwe'll first explore Rust's `f64` value which is a 64-bit float. This is \nequivalent to R's `double(1)`. We'll write a very simple Rust function that \nprints the value of the input and does not return anything. \n\n\n::: {.cell}\n\n```{.rust .cell-code}\n#[extendr]\nfn scalar_double(x: f64) { \n rprintln!(\"The value of x is {x}\"); \n}\n```\n:::\n\n\n::: callout-note\nNote the use of `rprintln!()` instead of the `println!()` macro.\nUsing `println!()` will not always be captured by the R console. Using\n`rprintln!()` will ensure that it is. \n:::\n\nIf you are not working inside of an extendr R package, you can create this function locally\nusing `rextendr::rust_function()`.\n\n```r\nrextendr::rust_function(\"\nfn scalar_double(x: f64) { \n rprintln!(\"The value of x is {x}\"); \n}\n\")\n```\n\nTry calling this function on a single double value. \n\n\n::: {.cell}\n\n```{.r .cell-code}\nscalar_double(4.2)\n```\n\n::: {.cell-output .cell-output-stdout}\n\n```\nThe value of x is 4.2\n```\n\n\n:::\n:::\n\n\nA couple of things to note with this example. First, `x: f64` tells Rust that\nthe type of `x` being passed to the function is a single double vector or \"float\"\nvalue. Second, `rprintln!(\"{}\", x);` is an extendr macro (the give-away for this\nis the `!`) that makes it easier to print information from Rust to the console\nin R. R users will perhaps notice that the syntax is vaguely `{glue}`-like in\nthat the value of x is inserted into the curly brackets.\n\nNow, what if, rather than printing the value of `x` to the R console, we wanted\ninstead to return that value to R? To do that, we just need to let Rust know\nwhat type is being returned by our function. This is done with the `-> type`\nnotation. The extendr crate knows how to handle the scalar `f64` type and pass\nit to R as double.\n\n\n::: {.cell}\n\n```{.rust .cell-code}\nfn scalar_double(x: f64) -> f64 { \n x \n}\n```\n:::\n\n::: {.cell}\n\n```{.r .cell-code}\nx <- scalar_double(4.2)\n```\n\n::: {.cell-output .cell-output-stdout}\n\n```\nThe value of x is 4.2\n```\n\n\n:::\n\n```{.r .cell-code}\ntypeof(x)\n```\n\n::: {.cell-output .cell-output-stdout}\n\n```\n[1] \"NULL\"\n```\n\n\n:::\n\n```{.r .cell-code}\nx + 1\n```\n\n::: {.cell-output .cell-output-stdout}\n\n```\nnumeric(0)\n```\n\n\n:::\n:::\n\n\n### Additional examples\n\nWe can extend this example to `i32`, `bool` and `String` values in Rust. \n\n\n::: {.cell}\n\n```{.rust .cell-code}\n#[extendr]\nfn scalar_integer(x: i32) -> i32 { x }\n\n#[extendr]\nfn scalar_logical(x: bool) -> bool { x }\n\n#[extendr]\nfn scalar_character(x: String) -> String { x }\n```\n:::\n\n::: {.cell}\n\n```{.r .cell-code}\nscalar_integer(4L)\n```\n\n::: {.cell-output .cell-output-stdout}\n\n```\n[1] 4\n```\n\n\n:::\n\n```{.r .cell-code}\nscalar_logical(TRUE)\n```\n\n::: {.cell-output .cell-output-stdout}\n\n```\n[1] TRUE\n```\n\n\n:::\n\n```{.r .cell-code}\nscalar_character(\"Hello world!\")\n```\n\n::: {.cell-output .cell-output-stdout}\n\n```\n[1] \"Hello world!\"\n```\n\n\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```{.r .cell-code}\nscalar_double(c(4.2, 1.3, 2.5))\n```\n\n::: {.cell-output .cell-output-error}\n\n```\nError in scalar_double(c(4.2, 1.3, 2.5)): Expected Scalar, got Doubles\n```\n\n\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\nnot recommended. Instead, extendr types should be used as they provide\naccess directly to R objectes. Whereas using Rust vectors requires \nadditional allocations. \n:::\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 extendr.\n\n| R type | extendr type | Rust type |\n|---------------|--------------|---------------------|\n| `integer()` | `Integers` | `Vec` |\n| `double()` | `Doubles` | `Vec` |\n| `complex()` | `Complexes` | `Vec>` |\n| `character()` | `Strings` | `Vec` |\n| `raw()` | `Raw` | `&[u8]` |\n| `logical()` | `Logicals` | |\n| `list()` | `List` | |\n\n::: callout-note\nYou might have anticipated `Vec` to be a supported Rust \nvector type. This is not possible because in R, logical vectors\ndo not contain _only_ `true` and `false` like Rust's bool type. \nThey also can be an `NA` value which has no corresponding representation\nin Rust. \n:::\n\n\nBelow defines Rust function which takes in a vector of `f64` values and prints them out. \n\n\n::: {.cell}\n\n```{.rust .cell-code}\n#[extendr]\nfn vector_double(x: Vec) {\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 [Display](https://doc.rust-lang.org/std/fmt/trait.Display.html) trait so the debug format (`:?`) is used.\n:::\n\n\n::: {.cell}\n\n```{.r .cell-code}\nvector_double(c(4.2, 1.3, 2.5))\n```\n\n::: {.cell-output .cell-output-stdout}\n\n```\nThe values of x are [4.2, 1.3, 2.5]\n```\n\n\n:::\n:::\n\n\n\nReturning values using Rust follows the same rules as R. You do not need to explicitly return a value as long as the last item in an expression is not followed by a `;`. \n\n\n::: {.cell}\n\n```{.rust .cell-code}\n#[extendr]\nfn vector_double(x: Vec) -> Vec { \n x \n}\n```\n:::\n\n\nCalling the function returns the input as a double vector\n\n::: {.cell}\n\n```{.r .cell-code}\nx <- vector_double(c(4.2, 1.3, 2.5))\ntypeof(x)\n```\n\n::: {.cell-output .cell-output-stdout}\n\n```\n[1] \"double\"\n```\n\n\n:::\n\n```{.r .cell-code}\nx + 1\n```\n\n::: {.cell-output .cell-output-stdout}\n\n```\n[1] 5.2 2.3 3.5\n```\n\n\n:::\n:::\n\n\n### Additional examples\n\nThese same principles can be extended to other supported vector types such as `Vec` and `Vec`.\n\n\n::: {.cell}\n\n```{.rust .cell-code}\n#[extendr]\nfn vector_integer(x: Vec) -> Vec { \n x\n}\n\n#[extendr]\nfn vector_character(x: Vec) -> Vec {\n x \n}\n```\n:::\n\n::: {.cell}\n\n```{.r .cell-code}\nvector_integer(c(4L, 6L, 8L))\n```\n\n::: {.cell-output .cell-output-stdout}\n\n```\n[1] 4 6 8\n```\n\n\n:::\n\n```{.r .cell-code}\nvector_character(c(\"Hello world!\", \"Hello extendr!\", \"Hello R!\"))\n```\n\n::: {.cell-output .cell-output-stdout}\n\n```\n[1] \"Hello world!\" \"Hello extendr!\" \"Hello R!\" \n```\n\n\n:::\n:::\n\n\n## Missing values\n\nIn Rust, missing values do not exist this in part why using Rust types alone is insufficient. Below a simple function which adds 1 to the input is defined. \n\n\n::: {.cell}\n\n```{.rust .cell-code}\n#[extendr]\nfn plus_one(x: f64) -> f64 { \n x + 1.0 \n}\n```\n:::\n\n\nRunning this using a missing value results in an error. \n\n\n::: {.cell}\n\n```{.r .cell-code}\nplus_one(NA_real_)\n```\n\n::: {.cell-output .cell-output-error}\n\n```\nError in plus_one(NA_real_): Must not be NA.\n```\n\n\n:::\n:::\n\n\nThese extendr types, however, can be utilized much like a normal `f64` that is `NA` aware. You will see that we have replaced the Rust type\n`f64` with the extendr type `Rfloat`. Since `Rfloat` maps to a scalar value and not vector, the conversion needs to be handled more delicately. The macro was invoked with the `use_try_from = true` argument. This will eventually become the default behavior of extendr. \n\n\n::: {.cell}\n\n```{.rust .cell-code}\n#[extendr]\nfn plus_one(x: Rfloat) -> Rfloat { \n x + 1.0 \n}\n```\n:::\n\n::: {.cell}\n\n```{.r .cell-code}\nplus_one(NA_real_)\n```\n\n::: {.cell-output .cell-output-stdout}\n\n```\n[1] NA\n```\n\n\n:::\n\n```{.r .cell-code}\nplus_one(4.2)\n```\n\n::: {.cell-output .cell-output-stdout}\n\n```\n[1] 5.2\n```\n\n\n:::\n:::\n\n\nThe combination of these two changes allows us to pass missing values to our `plus_one()` function and return\nmissing values without raising an error.\n", + "supporting": [], + "filters": [ + "rmarkdown/pagebreak.lua" + ], + "includes": {}, + "engineDependencies": {}, + "preserve": {}, + "postProcess": true + } +} \ No newline at end of file diff --git a/_freeze/user-guide/complete-example/execute-results/html.json b/_freeze/user-guide/complete-example/execute-results/html.json new file mode 100644 index 0000000..aec2d49 --- /dev/null +++ b/_freeze/user-guide/complete-example/execute-results/html.json @@ -0,0 +1,15 @@ +{ + "hash": "017d253141d50e842b134e50a8ceacff", + "result": { + "engine": "knitr", + "markdown": "---\ntitle: \"A Complete Example\"\nsubtitle: \"A package from start to finish: Making a heckin' case converter.\"\n---\n\n\nThe Rust crate ecosystem is rich with very small and very powerful utility libraries. One of the most downloaded crates is [heck](https://docs.rs/heck). It provides traits and structs to perform some of the most common case conversions.\n\nIn this tutorial we'll create a 0 dependency R package to provide the common case conversions. The resultant R package will be more performant but less flexible than the [`{snakecase}`](https://tazinho.github.io/snakecase/) R package. \n\nThis tutorial covers: \n\n- vectorization\n- `NA` handling\n- code generation using a macro\n\n## Getting started\n\nCreate a new R package:\n\n```r\nusethis::create_package(\"heck\")\n```\n\nWhen the new R package has opened up, add `extendr`.\n\n```r\nrextendr::use_extendr(crate_name = \"rheck\", lib_name = \"rheck\")\n```\n\n::: callout-note\nWhen adding the extendr dependency, make sure that the `crate_name` and `lib_name` arguments _are not_ `heck`. In order to add the `heck` crate as a dependency, the crate itself cannot be called `heck` because it creates a recursive dependency. Doing this allows us to name the R package `{heck}`, but the internal Rust crate is called `rheck`.\n:::\n\nNext, `heck` is needed as a dependency. From your terminal, navigate to `src/rust` and run `cargo add heck`. With this, you have everything you need to get started.\n\n\n## snek case conversion\n\n\n\n\n::: {.cell}\n\n```{.rust .cell-code}\nuse heck::ToSnekCase;\n```\n:::\n\n\nLet's start by creating a simple function to take a single string, and convert it to snake case. First, the trait `ToSnekCase` needs to be imported so that the method `to_snek_case()` is available to `&str`.\n\n\n::: {.cell}\n\n```{.rust .cell-code}\nuse heck::ToSnekCase;\n\n#[extendr]\nfn to_snek_case(x: &str) -> String {\n x.to_snek_case()\n}\n```\n:::\n\n\nSimple enough, right? Let's give it a shot. To make it accessible from your R session, it needs to be included in your `extendr_module! {}` macro. \n\n```rust\nextendr_module! {\n mod heck;\n fn to_snek_case;\n}\n```\n\nFrom your R session, run `rextendr::document()` followed by `devtools::load_all()` to make the function available. We'll skip these step from now on, but be sure to remember it!\n\n\n::: {.cell}\n\n```{.r .cell-code}\nto_snek_case(\"MakeMe-Snake case\")\n```\n\n::: {.cell-output .cell-output-stdout}\n\n```\n[1] \"make_me_snake_case\"\n```\n\n\n:::\n:::\n\n\nRarely is it useful to run a function on just a scalar character value. Rust, though, works with scalars by default and adding vectorization is another step. \n\n\n::: {.cell}\n\n```{.r .cell-code}\nto_snek_case(c(\"DontStep\", \"on-Snek\"))\n```\n\n::: {.cell-output .cell-output-error}\n\n```\nError in to_snek_case(c(\"DontStep\", \"on-Snek\")): Expected Scalar, got Strings\n```\n\n\n:::\n:::\n\n\nProviding a character vector causes an error. So how do you go about vectorizing? \n\n## vectorizing snek case conversion\n\nTo vectorize this function, you need to be apply the conversion to each element in a character vector. The extendr wrapper struct for a character vector is called `Strings`. To take in a character vector and also return one, the function signature should look like this:\n\n```rust\n#[extendr]\nfn to_snek_case(x: Strings) -> Strings {\n}\n```\n\nThis says there is an argument `x` which must be a character vector and this function must also `->` return the `Strings` (a character vector).\n\nTo iterate through this you can use the `.into_iter()` method on the character vector. \n\n```rust\n#[extendr]\nfn to_snek_case(x: Strings) -> Strings {\n x\n .into_iter()\n // the rest of the function\n}\n```\n\nIterators have a method called `.map()` (yes, just like `purrr::map()`). It lets you apply a closure (an anonymous function) to each element of the iterator. In this case, each element is an [`Rstr`](https://extendr.github.io/extendr/extendr_api/wrapper/rstr/struct.Rstr.html). The `Rstr` has a method `.as_str()` which will return a string slice `&str`. You can take this slice and pass it on to `.to_snek_case()`. After having mapped over each element, the results are `.collect()`ed into another `Strings`. \n\n\n\n::: {.cell preamble='use_heck'}\n\n```{.rust .cell-code}\n#[extendr]\nfn to_snek_case(x: Strings) -> Strings {\n x\n .into_iter()\n .map(|xi| {\n xi.as_str().to_snek_case()\n })\n .collect::()\n}\n```\n:::\n\n\n\nThis new version of the function can be used in a vectorized manner: \n\n\n::: {.cell}\n\n```{.r .cell-code}\nto_snek_case(c(\"DontStep\", \"on-Snek\"))\n```\n\n::: {.cell-output .cell-output-stdout}\n\n```\n[1] \"dont_step\" \"on_snek\" \n```\n\n\n:::\n:::\n\n\nBut can it handle a missing value out of the box? \n\n\n::: {.cell}\n\n```{.r .cell-code}\nto_snek_case(c(\"DontStep\", NA_character_, \"on-Snek\"))\n```\n\n::: {.cell-output .cell-output-stdout}\n\n```\n[1] \"dont_step\" \"na\" \"on_snek\" \n```\n\n\n:::\n:::\n\n\nWell, sort of. The `as_str()` method when used on a missing value will return `\"NA\"` which is not in a user's best interest. \n\n\n## handling missing values\n\nInstead of returning `\"na\"`, it would be better to return an _actual_ missing value. Those can be created each scalar's `na()` method e.g. `Rstr::na()`. \n\nYou can modify the `.map()` statement to check if an `NA` is present, and, if so, return an `NA` value. To perform this check, use the `is_na()` method which returns a `bool` which is either `true` or `false`. The result can be [`match`ed](https://doc.rust-lang.org/book/ch06-02-match.html). When it is missing, the match arm returns the `NA` scalar value. When it is not missing, the `Rstr` is converted to snek case. However, since the `true` arm is an `Rstr` the other `false` arm must _also_ be an `Rstr`. To accomplish this use the `Rstr::from()` method. \n\n\n::: {.cell preamble='use_heck' profile='release'}\n\n```{.rust .cell-code}\n#[extendr]\nfn to_snek_case(x: Strings) -> Strings {\n x.into_iter()\n .map(|xi| match xi.is_na() {\n true => Rstr::na(),\n false => Rstr::from(xi.as_str().to_snek_case()),\n })\n .collect::()\n}\n```\n:::\n\n\nThis function can now handle missing values! \n\n\n::: {.cell}\n\n```{.r .cell-code}\nto_snek_case(c(\"DontStep\", NA_character_, \"on-Snek\"))\n```\n\n::: {.cell-output .cell-output-stdout}\n\n```\n[1] \"dont_step\" NA \"on_snek\" \n```\n\n\n:::\n:::\n\n\n## automating other methods with a macro! \n\nThere are traits for the other case conversions such as `ToKebabCase`, `ToPascalCase`, `ToShoutyKebabCase` and others. The each have a similar method name: `.to_kebab_case()`, `to_pascal_case()`, `.to_shouty_kebab_case()`. You can either choose to copy the above and change the method call multiple times, _or_ use a macro as a form of code generation. \n\nA macro allows you to generate code in a short hand manner. This macro take an identifier which has a placeholder called `$fn_name`: `$fn_name:ident`. \n\n```rust\nmacro_rules! make_heck_fn {\n ($fn_name:ident) => {\n #[extendr]\n /// @export\n fn $fn_name(x: Strings) -> Strings {\n x.into_iter()\n .map(|xi| match xi.is_na() {\n true => Rstr::na(),\n false => Rstr::from(xi.as_str().$fn_name()),\n })\n .collect::()\n }\n };\n}\n```\n\nThe `$fn_name` placeholder is put as the function name definition which is the same as the method name. To use this macro to generate the rest of the functions the other traits need to be imported.\n\n\n::: {.cell}\n\n```{.rust .cell-code}\nuse heck::{\n ToKebabCase, ToShoutyKebabCase,\n ToSnekCase, ToShoutySnakeCase,\n ToPascalCase, ToUpperCamelCase,\n ToTrainCase, ToTitleCase,\n};\n```\n:::\n\n\nWith the traits in scope, the macro can be invoked to generate the other functions.\n\n```rust\nmake_heck_fn!(to_snek_case);\nmake_heck_fn!(to_shouty_snake_case);\nmake_heck_fn!(to_kebab_case);\nmake_heck_fn!(to_shouty_kebab_case);\nmake_heck_fn!(to_pascal_case);\nmake_heck_fn!(to_upper_camel_case);\nmake_heck_fn!(to_train_case);\nmake_heck_fn!(to_title_case);\n```\n\nNote that each of these functions should be added to the `extendr_module! {}` macro in order for them to be available from R. \n\n\n\n\n\nTest it out with the `to_shouty_kebab_case()` function! \n\n\n::: {.cell}\n\n```{.r .cell-code}\nto_shouty_kebab_case(\"lorem:IpsumDolor__sit^amet\")\n```\n\n::: {.cell-output .cell-output-stdout}\n\n```\n[1] \"LOREM-IPSUM-DOLOR-SIT-AMET\"\n```\n\n\n:::\n:::\n\n\nAnd with that, you've created an R package that provides case conversion using heck and with very little code!\n\n\n## bench marking with `{snakecase}`\n\nTo illustrate the performance gains from using a vectorized Rust funciton, a `bench::mark()` is created between `to_snek_case()` and `snakecase::to_snake_case()`.\n\n\n\n\n\nThe bench mark will use 5000 randomly generated lorem ipsum sentences. \n\n\n::: {.cell}\n\n```{.r .cell-code}\nx <- unlist(lorem::ipsum(5000, 1, 25))\n\nhead(x)\n```\n\n::: {.cell-output .cell-output-stdout}\n\n```\n[1] \"Adipiscing porttitor ac gravida scelerisque quisque sapien cum est duis metus penatibus cras himenaeos interdum senectus sollicitudin nisi malesuada faucibus semper sodales potenti rutrum eu?\"\n[2] \"Ipsum laoreet laoreet laoreet rutrum senectus id lectus erat velit mattis felis tellus mus neque augue libero dapibus auctor cursus lacinia egestas nullam ultrices ultrices porttitor cursus?\" \n[3] \"Amet sollicitudin a erat sagittis id massa lobortis justo metus vehicula purus ad orci non et magnis dignissim metus dictum diam euismod a?\" \n[4] \"Sit torquent vivamus blandit molestie semper primis pretium tempus faucibus nisi magnis sed cursus vehicula venenatis praesent pretium ad montes lectus a vel magna arcu praesent dui aenean?\" \n[5] \"Lorem platea magnis interdum metus cubilia venenatis elementum habitant morbi iaculis aenean sagittis metus luctus conubia quisque tempus pellentesque inceptos sed nisi?\" \n[6] \"Lorem tristique senectus orci diam parturient non ut nunc condimentum enim parturient mus neque vel porttitor lacus litora fusce sociosqu mauris ligula auctor?\" \n```\n\n\n:::\n\n```{.r .cell-code}\nbench::mark(\n rust = to_snek_case(x),\n snakecase = snakecase::to_snake_case(x)\n)\n```\n\n::: {.cell-output .cell-output-stdout}\n\n```\n# A tibble: 2 × 6\n expression min median `itr/sec` mem_alloc `gc/sec`\n \n1 rust 23.7ms 24.3ms 40.4 1.17MB 0 \n2 snakecase 305.8ms 307ms 3.26 12.29MB 4.89\n```\n\n\n:::\n:::\n\n\n\n\n## The whole thing\n\nIn just 42 lines of code (empty lines included), you can create a very performant R package! \n\n```rust\nuse extendr_api::prelude::*;\n\nuse heck::{\n ToKebabCase, ToPascalCase, ToShoutyKebabCase, ToShoutySnakeCase, ToSnekCase, ToTitleCase,\n ToTrainCase, ToUpperCamelCase,\n};\n\nmacro_rules! make_heck_fn {\n ($fn_name:ident) => {\n #[extendr]\n /// @export\n fn $fn_name(x: Strings) -> Strings {\n x.into_iter()\n .map(|xi| match xi.is_na() {\n true => Rstr::na(),\n false => Rstr::from(xi.as_str().$fn_name()),\n })\n .collect::()\n }\n };\n}\n\nmake_heck_fn!(to_snek_case);\nmake_heck_fn!(to_shouty_snake_case);\nmake_heck_fn!(to_kebab_case);\nmake_heck_fn!(to_shouty_kebab_case);\nmake_heck_fn!(to_pascal_case);\nmake_heck_fn!(to_upper_camel_case);\nmake_heck_fn!(to_train_case);\nmake_heck_fn!(to_title_case);\n\nextendr_module! {\n mod heck;\n fn to_snek_case;\n fn to_shouty_snake_case;\n fn to_kebab_case;\n fn to_shouty_kebab_case;\n fn to_pascal_case;\n fn to_upper_camel_case;\n fn to_title_case;\n fn to_train_case;\n}\n```", + "supporting": [], + "filters": [ + "rmarkdown/pagebreak.lua" + ], + "includes": {}, + "engineDependencies": {}, + "preserve": {}, + "postProcess": true + } +} \ No newline at end of file diff --git a/_freeze/user-guide/heckin-case-converter/execute-results/html.json b/_freeze/user-guide/heckin-case-converter/execute-results/html.json new file mode 100644 index 0000000..c89839a --- /dev/null +++ b/_freeze/user-guide/heckin-case-converter/execute-results/html.json @@ -0,0 +1,15 @@ +{ + "hash": "017d253141d50e842b134e50a8ceacff", + "result": { + "engine": "knitr", + "markdown": "---\ntitle: \"A Complete Example\"\nsubtitle: \"A package from start to finish: Making a heckin' case converter.\"\n---\n\n\nThe Rust crate ecosystem is rich with very small and very powerful utility libraries. One of the most downloaded crates is [heck](https://docs.rs/heck). It provides traits and structs to perform some of the most common case conversions.\n\nIn this tutorial we'll create a 0 dependency R package to provide the common case conversions. The resultant R package will be more performant but less flexible than the [`{snakecase}`](https://tazinho.github.io/snakecase/) R package. \n\nThis tutorial covers: \n\n- vectorization\n- `NA` handling\n- code generation using a macro\n\n## Getting started\n\nCreate a new R package:\n\n```r\nusethis::create_package(\"heck\")\n```\n\nWhen the new R package has opened up, add `extendr`.\n\n```r\nrextendr::use_extendr(crate_name = \"rheck\", lib_name = \"rheck\")\n```\n\n::: callout-note\nWhen adding the extendr dependency, make sure that the `crate_name` and `lib_name` arguments _are not_ `heck`. In order to add the `heck` crate as a dependency, the crate itself cannot be called `heck` because it creates a recursive dependency. Doing this allows us to name the R package `{heck}`, but the internal Rust crate is called `rheck`.\n:::\n\nNext, `heck` is needed as a dependency. From your terminal, navigate to `src/rust` and run `cargo add heck`. With this, you have everything you need to get started.\n\n\n## snek case conversion\n\n\n\n\n::: {.cell}\n\n```{.rust .cell-code}\nuse heck::ToSnekCase;\n```\n:::\n\n\nLet's start by creating a simple function to take a single string, and convert it to snake case. First, the trait `ToSnekCase` needs to be imported so that the method `to_snek_case()` is available to `&str`.\n\n\n::: {.cell}\n\n```{.rust .cell-code}\nuse heck::ToSnekCase;\n\n#[extendr]\nfn to_snek_case(x: &str) -> String {\n x.to_snek_case()\n}\n```\n:::\n\n\nSimple enough, right? Let's give it a shot. To make it accessible from your R session, it needs to be included in your `extendr_module! {}` macro. \n\n```rust\nextendr_module! {\n mod heck;\n fn to_snek_case;\n}\n```\n\nFrom your R session, run `rextendr::document()` followed by `devtools::load_all()` to make the function available. We'll skip these step from now on, but be sure to remember it!\n\n\n::: {.cell}\n\n```{.r .cell-code}\nto_snek_case(\"MakeMe-Snake case\")\n```\n\n::: {.cell-output .cell-output-stdout}\n\n```\n[1] \"make_me_snake_case\"\n```\n\n\n:::\n:::\n\n\nRarely is it useful to run a function on just a scalar character value. Rust, though, works with scalars by default and adding vectorization is another step. \n\n\n::: {.cell}\n\n```{.r .cell-code}\nto_snek_case(c(\"DontStep\", \"on-Snek\"))\n```\n\n::: {.cell-output .cell-output-error}\n\n```\nError in to_snek_case(c(\"DontStep\", \"on-Snek\")): Expected Scalar, got Strings\n```\n\n\n:::\n:::\n\n\nProviding a character vector causes an error. So how do you go about vectorizing? \n\n## vectorizing snek case conversion\n\nTo vectorize this function, you need to be apply the conversion to each element in a character vector. The extendr wrapper struct for a character vector is called `Strings`. To take in a character vector and also return one, the function signature should look like this:\n\n```rust\n#[extendr]\nfn to_snek_case(x: Strings) -> Strings {\n}\n```\n\nThis says there is an argument `x` which must be a character vector and this function must also `->` return the `Strings` (a character vector).\n\nTo iterate through this you can use the `.into_iter()` method on the character vector. \n\n```rust\n#[extendr]\nfn to_snek_case(x: Strings) -> Strings {\n x\n .into_iter()\n // the rest of the function\n}\n```\n\nIterators have a method called `.map()` (yes, just like `purrr::map()`). It lets you apply a closure (an anonymous function) to each element of the iterator. In this case, each element is an [`Rstr`](https://extendr.github.io/extendr/extendr_api/wrapper/rstr/struct.Rstr.html). The `Rstr` has a method `.as_str()` which will return a string slice `&str`. You can take this slice and pass it on to `.to_snek_case()`. After having mapped over each element, the results are `.collect()`ed into another `Strings`. \n\n\n\n::: {.cell preamble='use_heck'}\n\n```{.rust .cell-code}\n#[extendr]\nfn to_snek_case(x: Strings) -> Strings {\n x\n .into_iter()\n .map(|xi| {\n xi.as_str().to_snek_case()\n })\n .collect::()\n}\n```\n:::\n\n\n\nThis new version of the function can be used in a vectorized manner: \n\n\n::: {.cell}\n\n```{.r .cell-code}\nto_snek_case(c(\"DontStep\", \"on-Snek\"))\n```\n\n::: {.cell-output .cell-output-stdout}\n\n```\n[1] \"dont_step\" \"on_snek\" \n```\n\n\n:::\n:::\n\n\nBut can it handle a missing value out of the box? \n\n\n::: {.cell}\n\n```{.r .cell-code}\nto_snek_case(c(\"DontStep\", NA_character_, \"on-Snek\"))\n```\n\n::: {.cell-output .cell-output-stdout}\n\n```\n[1] \"dont_step\" \"na\" \"on_snek\" \n```\n\n\n:::\n:::\n\n\nWell, sort of. The `as_str()` method when used on a missing value will return `\"NA\"` which is not in a user's best interest. \n\n\n## handling missing values\n\nInstead of returning `\"na\"`, it would be better to return an _actual_ missing value. Those can be created each scalar's `na()` method e.g. `Rstr::na()`. \n\nYou can modify the `.map()` statement to check if an `NA` is present, and, if so, return an `NA` value. To perform this check, use the `is_na()` method which returns a `bool` which is either `true` or `false`. The result can be [`match`ed](https://doc.rust-lang.org/book/ch06-02-match.html). When it is missing, the match arm returns the `NA` scalar value. When it is not missing, the `Rstr` is converted to snek case. However, since the `true` arm is an `Rstr` the other `false` arm must _also_ be an `Rstr`. To accomplish this use the `Rstr::from()` method. \n\n\n::: {.cell preamble='use_heck' profile='release'}\n\n```{.rust .cell-code}\n#[extendr]\nfn to_snek_case(x: Strings) -> Strings {\n x.into_iter()\n .map(|xi| match xi.is_na() {\n true => Rstr::na(),\n false => Rstr::from(xi.as_str().to_snek_case()),\n })\n .collect::()\n}\n```\n:::\n\n\nThis function can now handle missing values! \n\n\n::: {.cell}\n\n```{.r .cell-code}\nto_snek_case(c(\"DontStep\", NA_character_, \"on-Snek\"))\n```\n\n::: {.cell-output .cell-output-stdout}\n\n```\n[1] \"dont_step\" NA \"on_snek\" \n```\n\n\n:::\n:::\n\n\n## automating other methods with a macro! \n\nThere are traits for the other case conversions such as `ToKebabCase`, `ToPascalCase`, `ToShoutyKebabCase` and others. The each have a similar method name: `.to_kebab_case()`, `to_pascal_case()`, `.to_shouty_kebab_case()`. You can either choose to copy the above and change the method call multiple times, _or_ use a macro as a form of code generation. \n\nA macro allows you to generate code in a short hand manner. This macro take an identifier which has a placeholder called `$fn_name`: `$fn_name:ident`. \n\n```rust\nmacro_rules! make_heck_fn {\n ($fn_name:ident) => {\n #[extendr]\n /// @export\n fn $fn_name(x: Strings) -> Strings {\n x.into_iter()\n .map(|xi| match xi.is_na() {\n true => Rstr::na(),\n false => Rstr::from(xi.as_str().$fn_name()),\n })\n .collect::()\n }\n };\n}\n```\n\nThe `$fn_name` placeholder is put as the function name definition which is the same as the method name. To use this macro to generate the rest of the functions the other traits need to be imported.\n\n\n::: {.cell}\n\n```{.rust .cell-code}\nuse heck::{\n ToKebabCase, ToShoutyKebabCase,\n ToSnekCase, ToShoutySnakeCase,\n ToPascalCase, ToUpperCamelCase,\n ToTrainCase, ToTitleCase,\n};\n```\n:::\n\n\nWith the traits in scope, the macro can be invoked to generate the other functions.\n\n```rust\nmake_heck_fn!(to_snek_case);\nmake_heck_fn!(to_shouty_snake_case);\nmake_heck_fn!(to_kebab_case);\nmake_heck_fn!(to_shouty_kebab_case);\nmake_heck_fn!(to_pascal_case);\nmake_heck_fn!(to_upper_camel_case);\nmake_heck_fn!(to_train_case);\nmake_heck_fn!(to_title_case);\n```\n\nNote that each of these functions should be added to the `extendr_module! {}` macro in order for them to be available from R. \n\n\n\n\n\nTest it out with the `to_shouty_kebab_case()` function! \n\n\n::: {.cell}\n\n```{.r .cell-code}\nto_shouty_kebab_case(\"lorem:IpsumDolor__sit^amet\")\n```\n\n::: {.cell-output .cell-output-stdout}\n\n```\n[1] \"LOREM-IPSUM-DOLOR-SIT-AMET\"\n```\n\n\n:::\n:::\n\n\nAnd with that, you've created an R package that provides case conversion using heck and with very little code!\n\n\n## bench marking with `{snakecase}`\n\nTo illustrate the performance gains from using a vectorized Rust funciton, a `bench::mark()` is created between `to_snek_case()` and `snakecase::to_snake_case()`.\n\n\n\n\n\nThe bench mark will use 5000 randomly generated lorem ipsum sentences. \n\n\n::: {.cell}\n\n```{.r .cell-code}\nx <- unlist(lorem::ipsum(5000, 1, 25))\n\nhead(x)\n```\n\n::: {.cell-output .cell-output-stdout}\n\n```\n[1] \"Lorem tempor facilisis fermentum posuere condimentum phasellus dignissim diam aenean ullamcorper blandit conubia natoque curae eget dignissim nibh feugiat vehicula phasellus aliquam mi sem ornare per.\"\n[2] \"Lorem nec cubilia primis tellus lacinia quis nullam per nisi est scelerisque commodo dictum in tempor cursus orci sollicitudin aliquam.\" \n[3] \"Amet ut class tellus dictum fusce erat in pharetra integer erat praesent mus in sagittis penatibus fermentum nisl vestibulum praesent hac integer luctus phasellus.\" \n[4] \"Amet curae non bibendum libero rhoncus laoreet eget tellus varius gravida nascetur inceptos libero maecenas natoque dui odio imperdiet.\" \n[5] \"Ipsum vulputate quisque sodales integer dapibus vestibulum facilisi euismod nec at nullam tincidunt a ac varius potenti dignissim risus morbi aliquam inceptos ultrices?\" \n[6] \"Dolor ultricies ligula id eleifend urna est risus ac urna turpis proin massa cum augue pulvinar inceptos integer blandit aptent semper nisl netus sapien.\" \n```\n\n\n:::\n\n```{.r .cell-code}\nbench::mark(\n rust = to_snek_case(x),\n snakecase = snakecase::to_snake_case(x)\n)\n```\n\n::: {.cell-output .cell-output-stdout}\n\n```\n# A tibble: 2 × 6\n expression min median `itr/sec` mem_alloc `gc/sec`\n \n1 rust 25.3ms 25.9ms 38.5 1.16MB 0 \n2 snakecase 321.3ms 323.4ms 3.09 12.28MB 4.64\n```\n\n\n:::\n:::\n\n\n\n\n## The whole thing\n\nIn just 42 lines of code (empty lines included), you can create a very performant R package! \n\n```rust\nuse extendr_api::prelude::*;\n\nuse heck::{\n ToKebabCase, ToPascalCase, ToShoutyKebabCase, ToShoutySnakeCase, ToSnekCase, ToTitleCase,\n ToTrainCase, ToUpperCamelCase,\n};\n\nmacro_rules! make_heck_fn {\n ($fn_name:ident) => {\n #[extendr]\n /// @export\n fn $fn_name(x: Strings) -> Strings {\n x.into_iter()\n .map(|xi| match xi.is_na() {\n true => Rstr::na(),\n false => Rstr::from(xi.as_str().$fn_name()),\n })\n .collect::()\n }\n };\n}\n\nmake_heck_fn!(to_snek_case);\nmake_heck_fn!(to_shouty_snake_case);\nmake_heck_fn!(to_kebab_case);\nmake_heck_fn!(to_shouty_kebab_case);\nmake_heck_fn!(to_pascal_case);\nmake_heck_fn!(to_upper_camel_case);\nmake_heck_fn!(to_train_case);\nmake_heck_fn!(to_title_case);\n\nextendr_module! {\n mod heck;\n fn to_snek_case;\n fn to_shouty_snake_case;\n fn to_kebab_case;\n fn to_shouty_kebab_case;\n fn to_pascal_case;\n fn to_upper_camel_case;\n fn to_title_case;\n fn to_train_case;\n}\n```", + "supporting": [], + "filters": [ + "rmarkdown/pagebreak.lua" + ], + "includes": {}, + "engineDependencies": {}, + "preserve": {}, + "postProcess": true + } +} \ No newline at end of file diff --git a/_freeze/user-guide/scalars/execute-results/html.json b/_freeze/user-guide/scalars/execute-results/html.json new file mode 100644 index 0000000..c2ae229 --- /dev/null +++ b/_freeze/user-guide/scalars/execute-results/html.json @@ -0,0 +1,15 @@ +{ + "hash": "6fc4dcad2ff549e86a05821eb9275471", + "result": { + "engine": "knitr", + "markdown": "---\ntitle: \"Scalar Type Mapping\"\n---\n\n::: {.cell}\n\n:::\n\n\nThis tutorial demonstrates some of the basics of passing data types back and\nforth between Rust and R. This includes all of the following:\n\n- Passing scalar types between R and Rust.\n- Passing vector types between R and Rust.\n- Printing from Rust to the console in R.\n- Handling missing values in Rust (a primer).\n\nWe'll start with examples showing how to pass R types as explicit Rust types.\nThis is useful for demonstration purposes, but it does ignore one very very big\nissue, and that's missing values. Rust data types do not allow for missing\nvalues, so they have to be handled carefully. Fortunately, extendr offers its\nown data types built on top of the Rust types to do that for you. For this\nreason, **it is strongly recommended that you work with the extendr types\nwherever possible.** However, when first getting comfortable with extendr, \nand possible even Rust, it may feel more comfortable to work with Rust\nnative types. \n\n## Scalar Type Mapping with Rust Types\n\nIn R, there is no such thing as a scalar value. Everything is a vector.\nWhen using a scalar value in R, that is really a length one vector. In Rust,\nhowever, scalar values are the building blocks of everything. \n\nBelow is a mapping of scalar values between R, extendr, and Rust. \n\n\n| R type | extendr type | Rust type |\n|----------------|--------------|----------------|\n| `integer(1)` | `Rint` | `i32` |\n| `double(1)` | `Rfloat` | `f64` |\n| `logical(1)` | `Rbool` | `bool` |\n| `complex(1)` | `Rcplx` | `Complex` |\n| `character(1)` | `Rstr` | `String` |\n\nTo see how these scalars get passed back and forth between Rust and R,\nwe'll first explore Rust's `f64` value which is a 64-bit float. This is \nequivalent to R's `double(1)`. We'll write a very simple Rust function that \nprints the value of the input and does not return anything. \n\n\n::: {.cell}\n\n```{.rust .cell-code}\n#[extendr]\nfn scalar_double(x: f64) { \n rprintln!(\"The value of x is {x}\"); \n}\n```\n:::\n\n\n::: callout-note\nNote the use of `rprintln!()` instead of the `println!()` macro.\nUsing `println!()` will not always be captured by the R console. Using\n`rprintln!()` will ensure that it is. \n:::\n\nIf you are not working inside of an extendr R package, you can create this function locally\nusing `rextendr::rust_function()`.\n\n```r\nrextendr::rust_function(\"\nfn scalar_double(x: f64) { \n rprintln!(\"The value of x is {x}\"); \n}\n\")\n```\n\nTry calling this function on a single double value. \n\n\n::: {.cell}\n\n```{.r .cell-code}\nscalar_double(4.2)\n```\n\n::: {.cell-output .cell-output-stdout}\n\n```\nThe value of x is 4.2\n```\n\n\n:::\n:::\n\n\nA couple of things to note with this example. First, `x: f64` tells Rust that\nthe type of `x` being passed to the function is a single double vector or \"float\"\nvalue. Second, `rprintln!(\"{}\", x);` is an extendr macro (the give-away for this\nis the `!`) that makes it easier to print information from Rust to the console\nin R. R users will perhaps notice that the syntax is vaguely `{glue}`-like in\nthat the value of x is inserted into the curly brackets.\n\nNow, what if, rather than printing the value of `x` to the R console, we wanted\ninstead to return that value to R? To do that, we just need to let Rust know\nwhat type is being returned by our function. This is done with the `-> type`\nnotation. The extendr crate knows how to handle the scalar `f64` type and pass\nit to R as double.\n\n\n::: {.cell}\n\n```{.rust .cell-code}\nfn scalar_double(x: f64) -> f64 { \n x \n}\n```\n:::\n\n::: {.cell}\n\n```{.r .cell-code}\nx <- scalar_double(4.2)\n```\n\n::: {.cell-output .cell-output-stdout}\n\n```\nThe value of x is 4.2\n```\n\n\n:::\n\n```{.r .cell-code}\ntypeof(x)\n```\n\n::: {.cell-output .cell-output-stdout}\n\n```\n[1] \"NULL\"\n```\n\n\n:::\n\n```{.r .cell-code}\nx + 1\n```\n\n::: {.cell-output .cell-output-stdout}\n\n```\nnumeric(0)\n```\n\n\n:::\n:::\n\n\n### Additional examples\n\nWe can extend this example to `i32`, `bool` and `String` values in Rust. \n\n\n::: {.cell}\n\n```{.rust .cell-code}\n#[extendr]\nfn scalar_integer(x: i32) -> i32 { x }\n\n#[extendr]\nfn scalar_logical(x: bool) -> bool { x }\n\n#[extendr]\nfn scalar_character(x: String) -> String { x }\n```\n:::\n\n::: {.cell}\n\n```{.r .cell-code}\nscalar_integer(4L)\n```\n\n::: {.cell-output .cell-output-stdout}\n\n```\n[1] 4\n```\n\n\n:::\n\n```{.r .cell-code}\nscalar_logical(TRUE)\n```\n\n::: {.cell-output .cell-output-stdout}\n\n```\n[1] TRUE\n```\n\n\n:::\n\n```{.r .cell-code}\nscalar_character(\"Hello world!\")\n```\n\n::: {.cell-output .cell-output-stdout}\n\n```\n[1] \"Hello world!\"\n```\n\n\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```{.r .cell-code}\nscalar_double(c(4.2, 1.3, 2.5))\n```\n\n::: {.cell-output .cell-output-error}\n\n```\nError in scalar_double(c(4.2, 1.3, 2.5)): Expected Scalar, got Doubles\n```\n\n\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\nnot recommended. Instead, extendr types should be used as they provide\naccess directly to R objectes. Whereas using Rust vectors requires \nadditional allocations. \n:::\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 extendr.\n\n| R type | extendr type | Rust type |\n|---------------|--------------|---------------------|\n| `integer()` | `Integers` | `Vec` |\n| `double()` | `Doubles` | `Vec` |\n| `complex()` | `Complexes` | `Vec>` |\n| `character()` | `Strings` | `Vec` |\n| `raw()` | `Raw` | `&[u8]` |\n| `logical()` | `Logicals` | |\n| `list()` | `List` | |\n\n::: callout-note\nYou might have anticipated `Vec` to be a supported Rust \nvector type. This is not possible because in R, logical vectors\ndo not contain _only_ `true` and `false` like Rust's bool type. \nThey also can be an `NA` value which has no corresponding representation\nin Rust. \n:::\n\n\nBelow defines Rust function which takes in a vector of `f64` values and prints them out. \n\n\n::: {.cell}\n\n```{.rust .cell-code}\n#[extendr]\nfn vector_double(x: Vec) {\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 [Display](https://doc.rust-lang.org/std/fmt/trait.Display.html) trait so the debug format (`:?`) is used.\n:::\n\n\n::: {.cell}\n\n```{.r .cell-code}\nvector_double(c(4.2, 1.3, 2.5))\n```\n\n::: {.cell-output .cell-output-stdout}\n\n```\nThe values of x are [4.2, 1.3, 2.5]\n```\n\n\n:::\n:::\n\n\n\nReturning values using Rust follows the same rules as R. You do not need to explicitly return a value as long as the last item in an expression is not followed by a `;`. \n\n\n::: {.cell}\n\n```{.rust .cell-code}\n#[extendr]\nfn vector_double(x: Vec) -> Vec { \n x \n}\n```\n:::\n\n\nCalling the function returns the input as a double vector\n\n::: {.cell}\n\n```{.r .cell-code}\nx <- vector_double(c(4.2, 1.3, 2.5))\ntypeof(x)\n```\n\n::: {.cell-output .cell-output-stdout}\n\n```\n[1] \"double\"\n```\n\n\n:::\n\n```{.r .cell-code}\nx + 1\n```\n\n::: {.cell-output .cell-output-stdout}\n\n```\n[1] 5.2 2.3 3.5\n```\n\n\n:::\n:::\n\n\n### Additional examples\n\nThese same principles can be extended to other supported vector types such as `Vec` and `Vec`.\n\n\n::: {.cell}\n\n```{.rust .cell-code}\n#[extendr]\nfn vector_integer(x: Vec) -> Vec { \n x\n}\n\n#[extendr]\nfn vector_character(x: Vec) -> Vec {\n x \n}\n```\n:::\n\n::: {.cell}\n\n```{.r .cell-code}\nvector_integer(c(4L, 6L, 8L))\n```\n\n::: {.cell-output .cell-output-stdout}\n\n```\n[1] 4 6 8\n```\n\n\n:::\n\n```{.r .cell-code}\nvector_character(c(\"Hello world!\", \"Hello extendr!\", \"Hello R!\"))\n```\n\n::: {.cell-output .cell-output-stdout}\n\n```\n[1] \"Hello world!\" \"Hello extendr!\" \"Hello R!\" \n```\n\n\n:::\n:::\n\n\n## Missing values\n\nIn Rust, missing values do not exist this in part why using Rust types alone is insufficient. Below a simple function which adds 1 to the input is defined. \n\n\n::: {.cell}\n\n```{.rust .cell-code}\n#[extendr]\nfn plus_one(x: f64) -> f64 { \n x + 1.0 \n}\n```\n:::\n\n\nRunning this using a missing value results in an error. \n\n\n::: {.cell}\n\n```{.r .cell-code}\nplus_one(NA_real_)\n```\n\n::: {.cell-output .cell-output-error}\n\n```\nError in plus_one(NA_real_): Must not be NA.\n```\n\n\n:::\n:::\n\n\nThese extendr types, however, can be utilized much like a normal `f64` that is `NA` aware. You will see that we have replaced the Rust type\n`f64` with the extendr type `Rfloat`. Since `Rfloat` maps to a scalar value and not vector, the conversion needs to be handled more delicately. The macro was invoked with the `use_try_from = true` argument. This will eventually become the default behavior of extendr. \n\n\n::: {.cell}\n\n```{.rust .cell-code}\n#[extendr]\nfn plus_one(x: Rfloat) -> Rfloat { \n x + 1.0 \n}\n```\n:::\n\n::: {.cell}\n\n```{.r .cell-code}\nplus_one(NA_real_)\n```\n\n::: {.cell-output .cell-output-stdout}\n\n```\n[1] NA\n```\n\n\n:::\n\n```{.r .cell-code}\nplus_one(4.2)\n```\n\n::: {.cell-output .cell-output-stdout}\n\n```\n[1] 5.2\n```\n\n\n:::\n:::\n\n\nThe combination of these two changes allows us to pass missing values to our `plus_one()` function and return\nmissing values without raising an error.\n", + "supporting": [], + "filters": [ + "rmarkdown/pagebreak.lua" + ], + "includes": {}, + "engineDependencies": {}, + "preserve": {}, + "postProcess": true + } +} \ No newline at end of file diff --git a/_freeze/user-guide/type-mapping/scalars/execute-results/html.json b/_freeze/user-guide/type-mapping/scalars/execute-results/html.json new file mode 100644 index 0000000..c495c82 --- /dev/null +++ b/_freeze/user-guide/type-mapping/scalars/execute-results/html.json @@ -0,0 +1,15 @@ +{ + "hash": "af8f7e730143ad4599e3a14572806e65", + "result": { + "engine": "knitr", + "markdown": "---\ntitle: \"Scalar Type Mapping\"\n---\n\n::: {.cell}\n\n:::\n\n\nThis tutorial demonstrates some of the basics of passing scalar data types back\nand forth between Rust and R. We'll start with simple examples using explicit\nRust types but then move on to showing their extendr alternatives. Why does\nextendr have its own data types? For a number of reasons, of course, but the\nmost important reason is probably that Rust types do not allow for missing\nvalues, so no `NA`, `NaN`, `NULL`, or what have you. Fortunately, extendr types\nwill handle missing values for you. For this reason, **it is strongly\nrecommended that you work with the extendr types whenever possible.**\n\n## Scalar types\n\nA scalar type consists of a single value, and it can *only* consist of a single\nvalue, whether that value is a single character string, integer, or logical. As\nit happens, R doesn't have a way of representing a scalar value. That's because\neverything is a vector in R, and vectors can have any arbitrary length you want.\nSo, the closest thing to a scalar you will ever encounter in R is a vector that\njust so happens to have a length of one. In Rust, however, scalars are the\nbuilding blocks of everything, and they come in a bewildering variety, at least\nfor the traditional R user. Consider, for example, integers. R has just one way \nto represent this type of numeric value. Rust, on the other hand, has twelve!\n\nThe table below shows the most common R \"scalar\" types, along with their Rust\nand extendr equivalents. \n\n| R type | extendr type | Rust type |\n|----------------|--------------|----------------|\n| `integer(1)` | `Rint` | `i32` |\n| `double(1)` | `Rfloat` | `f64` |\n| `logical(1)` | `Rbool` | `bool` |\n| `complex(1)` | `Rcplx` | `Complex` |\n| `character(1)` | `Rstr` | `String` |\n\nTo learn more about Rust types, see [section 3.2 of The\nBook](https://doc.rust-lang.org/book/ch03-02-data-types.html).\n\n## Sharing scalars\n\nTo see how scalars get passed back and forth between Rust and R, we'll first\nexplore Rust's `f64` value which is a 64-bit float. This is equivalent to R's\n`double(1)`. We'll write a very simple Rust function that prints the value of\nthe input and does not return anything.\n\n\n::: {.cell}\n\n```{.rust .cell-code}\n#[extendr]\nfn scalar_double(x: f64) { \n rprintln!(\"The value of x is {x}\"); \n}\n```\n:::\n\n\nThrough the magic of extendr, we can now call this function in R and pass it a \nsingle double value.\n\n\n::: {.cell}\n\n```{.r .cell-code}\nscalar_double(4.2)\n```\n\n::: {.cell-output .cell-output-stdout}\n\n```\nThe value of x is 4.2\n```\n\n\n:::\n:::\n\n\nThere are several things to note about this example. First, in Rust, `x: f64` \ntells us that the type of `x` being passed to the function (`fn`) is a single \ndouble vector or \"float\" value. Second, `rprintln!(\"{}\", x);` is an extendr \nmacro that makes it easier to print information from Rust to the console in R. \nR users will perhaps notice that the syntax is vaguely `{glue}`-like in that the \nvalue of `x` is inserted into the curly brackets. Finally, if you are not \nworking inside of an extendr R package, you can create the `scalar_double()` \nfunction locally using `rextendr::rust_function()`.\n\n``` r\nrextendr::rust_function(\"\nfn scalar_double(x: f64) { \n rprintln!(\"The value of x is {x}\"); \n}\n\")\n```\n\nNow, what if, rather than printing the value of `x` to the R console, we wanted\ninstead to return that value to R? To do that, we just need to let Rust know\nwhat type is being returned by our function. This is done with the `-> type`\nnotation. The extendr crate understands this notation and knows how to handle \nthe scalar `f64` type returned by the Rust function and pass it to R as double.\n\n\n::: {.cell}\n\n```{.rust .cell-code}\n#[extendr]\nfn return_scalar_double(x: f64) -> f64 { \n x \n}\n```\n:::\n\n::: {.cell}\n\n```{.r .cell-code}\nx <- return_scalar_double(4.2)\n\ntypeof(x)\n```\n\n::: {.cell-output .cell-output-stdout}\n\n```\n[1] \"double\"\n```\n\n\n:::\n\n```{.r .cell-code}\nx + 1.0\n```\n\n::: {.cell-output .cell-output-stdout}\n\n```\n[1] 5.2\n```\n\n\n:::\n:::\n\n\n## Missing values\n\nAs noted above, Rust does not allow a scalar type to have a missing value, so \nyou cannot simply pass a missing value like `NA` to Rust and expect it to just \nwork. Here is a demonstration of this issue using a simple function which adds \n1.0 to `x`.\n\n\n::: {.cell}\n\n```{.rust .cell-code}\n#[extendr]\nfn plus_one(x: f64) -> f64 { \n x + 1.0 \n}\n```\n:::\n\n\nYou will notice that this function expects `x` to be `f64`, not a missing value.\nPassing a missing value from R to this Rust function will, therefore, result in \nan error.\n\n\n::: {.cell}\n\n```{.r .cell-code}\nplus_one(NA_real_)\n```\n\n::: {.cell-output .cell-output-error}\n\n```\nError in plus_one(NA_real_): Must not be NA.\n```\n\n\n:::\n:::\n\n\nFortunately, the extendr types are `NA`-aware, so you can, for instance, use \nextendr's `Rfloat` in place of `f64` to handle missing values without error. \nBelow, you will see that we have done this for the function `plus_one()`. \n\n\n::: {.cell}\n\n```{.rust .cell-code}\n#[extendr]\nfn plus_one(x: Rfloat) -> Rfloat { \n x + 1.0 \n}\n```\n:::\n\n::: {.cell}\n\n```{.r .cell-code}\nplus_one(NA_real_)\n```\n\n::: {.cell-output .cell-output-stdout}\n\n```\n[1] NA\n```\n\n\n:::\n\n```{.r .cell-code}\nplus_one(4.2)\n```\n\n::: {.cell-output .cell-output-stdout}\n\n```\n[1] 5.2\n```\n\n\n:::\n:::\n\n\n## Additional examples\n\nHere are additional examples showing how to pass scalars to Rust and return them \nto R using Rust scalar types.\n\n\n::: {.cell}\n\n```{.rust .cell-code}\n#[extendr]\nfn scalar_integer(x: i32) -> i32 { x }\n\n#[extendr]\nfn scalar_logical(x: bool) -> bool { x }\n```\n:::\n\n::: {.cell}\n\n```{.r .cell-code}\nscalar_integer(4L)\n```\n\n::: {.cell-output .cell-output-stdout}\n\n```\n[1] 4\n```\n\n\n:::\n\n```{.r .cell-code}\nscalar_logical(TRUE)\n```\n\n::: {.cell-output .cell-output-stdout}\n\n```\n[1] TRUE\n```\n\n\n:::\n:::\n\n\nAnd here are the same examples with extendr scalar types.\n\n\n::: {.cell}\n\n```{.rust .cell-code}\n#[extendr]\nfn scalar_integer(x: Rint) -> Rint { x }\n\n#[extendr]\nfn scalar_logical(x: Rbool) -> Rbool { x }\n```\n:::\n\n::: {.cell}\n\n```{.r .cell-code}\nscalar_integer(4L)\n```\n\n::: {.cell-output .cell-output-stdout}\n\n```\n[1] 4\n```\n\n\n:::\n\n```{.r .cell-code}\nscalar_logical(TRUE)\n```\n\n::: {.cell-output .cell-output-stdout}\n\n```\n[1] TRUE\n```\n\n\n:::\n:::\n\n\nDid you notice that we didn't give an example with character strings? Yeah, well,\nthere's a good reason for that. You can find out what that is by heading over to\nthe tutorial on [Handling Strings](user-guide/type-mapping/characters.qmd).", + "supporting": [], + "filters": [ + "rmarkdown/pagebreak.lua" + ], + "includes": {}, + "engineDependencies": {}, + "preserve": {}, + "postProcess": true + } +} \ No newline at end of file diff --git a/_freeze/user-guide/type-mapping/vectors/execute-results/html.json b/_freeze/user-guide/type-mapping/vectors/execute-results/html.json new file mode 100644 index 0000000..7beae4a --- /dev/null +++ b/_freeze/user-guide/type-mapping/vectors/execute-results/html.json @@ -0,0 +1,15 @@ +{ + "hash": "f1c04cd553e224259a06a758fee2eb97", + "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```{.r .cell-code}\nscalar_double(c(4.2, 1.3, 2.5))\n```\n\n::: {.cell-output .cell-output-error}\n\n```\nError in scalar_double(c(4.2, 1.3, 2.5)): could not find function \"scalar_double\"\n```\n\n\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` |\n| `double()` | `Doubles` | `Vec` |\n| `complex()` | `Complexes` | `Vec>` |\n| `character()` | `Strings` | `Vec` |\n| `raw()` | `Raw` | `&[u8]` |\n| `logical()` | `Logicals` | |\n| `list()` | `List` | |\n\n::: callout-note\nYou might have anticipated `Vec` 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) {\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```\n\n::: {.cell-output .cell-output-stdout}\n\n```\nThe values of x are [4.2, 1.3, 2.5]\n```\n\n\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) -> Vec { \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```\n\n::: {.cell-output .cell-output-stdout}\n\n```\n[1] \"double\"\n```\n\n\n:::\n\n```{.r .cell-code}\nx + 1\n```\n\n::: {.cell-output .cell-output-stdout}\n\n```\n[1] 5.2 2.3 3.5\n```\n\n\n:::\n:::\n\n\n### Additional examples\n\nThese same principles can be extended to other supported vector types such as\n`Vec` and `Vec`.\n\n\n::: {.cell}\n\n```{.rust .cell-code}\n#[extendr]\nfn vector_integer(x: Vec) -> Vec { \n x\n}\n\n#[extendr]\nfn vector_character(x: Vec) -> Vec {\n x \n}\n```\n:::\n\n::: {.cell}\n\n```{.r .cell-code}\nvector_integer(c(4L, 6L, 8L))\n```\n\n::: {.cell-output .cell-output-stdout}\n\n```\n[1] 4 6 8\n```\n\n\n:::\n\n```{.r .cell-code}\nvector_character(c(\"Hello world!\", \"Hello extendr!\", \"Hello R!\"))\n```\n\n::: {.cell-output .cell-output-stdout}\n\n```\n[1] \"Hello world!\" \"Hello extendr!\" \"Hello R!\" \n```\n\n\n:::\n:::", + "supporting": [], + "filters": [ + "rmarkdown/pagebreak.lua" + ], + "includes": {}, + "engineDependencies": {}, + "preserve": {}, + "postProcess": true + } +} \ No newline at end of file diff --git a/_quarto.yml b/_quarto.yml index 16455b2..0db4584 100644 --- a/_quarto.yml +++ b/_quarto.yml @@ -6,10 +6,13 @@ execute: website: title: "extendr" + navbar: left: - text: "Get Started" href: get-started.qmd + - text: "User Guide" + href: user-guide/index.qmd - text: Contributing href: CONTRIBUTING.md - text: "News" @@ -24,6 +27,23 @@ website: - icon: discord href: https://discord.gg/7hmApuc + sidebar: + - title: "User Guide" + collapse-level: 1 + style: "floating" + contents: + - user-guide/index.qmd + - user-guide/complete-example.qmd + - section: "Type Mapping" + contents: + - user-guide/type-mapping/scalars.qmd + - user-guide/type-mapping/vectors.qmd + - user-guide/type-mapping/missing-values.qmd + - user-guide/type-mapping/characters.qmd + - section: "Error Handling" + contents: + - user-guide/error-handling/basic-error-handling.qmd + format: html: theme: diff --git a/user-guide/_metadata.yml b/user-guide/_metadata.yml new file mode 100644 index 0000000..01dec92 --- /dev/null +++ b/user-guide/_metadata.yml @@ -0,0 +1,9 @@ +format: + html: + toc: true + toc-location: right + +knitr: + opts_chunk: + collapse: true + comment: "#>" \ No newline at end of file diff --git a/user-guide/complete-example.qmd b/user-guide/complete-example.qmd new file mode 100644 index 0000000..8b1d408 --- /dev/null +++ b/user-guide/complete-example.qmd @@ -0,0 +1,312 @@ +--- +title: "A Complete Example" +subtitle: "A package from start to finish: Making a heckin' case converter." +--- + +The Rust crate ecosystem is rich with very small and very powerful utility libraries. One of the most downloaded crates is [heck](https://docs.rs/heck). It provides traits and structs to perform some of the most common case conversions. + +In this tutorial we'll create a 0 dependency R package to provide the common case conversions. The resultant R package will be more performant but less flexible than the [`{snakecase}`](https://tazinho.github.io/snakecase/) R package. + +This tutorial covers: + +- vectorization +- `NA` handling +- code generation using a macro + +## Getting started + +Create a new R package: + +```r +usethis::create_package("heck") +``` + +When the new R package has opened up, add `extendr`. + +```r +rextendr::use_extendr(crate_name = "rheck", lib_name = "rheck") +``` + +::: callout-note +When adding the extendr dependency, make sure that the `crate_name` and `lib_name` arguments _are not_ `heck`. In order to add the `heck` crate as a dependency, the crate itself cannot be called `heck` because it creates a recursive dependency. Doing this allows us to name the R package `{heck}`, but the internal Rust crate is called `rheck`. +::: + +Next, `heck` is needed as a dependency. From your terminal, navigate to `src/rust` and run `cargo add heck`. With this, you have everything you need to get started. + + +## snek case conversion + +```{r, include = FALSE} +library(rextendr) +knitr::opts_chunk$set(engine.opts = list(dependencies = list(heck = "0.5.0"))) +``` + +```{extendrsrc use_heck} +use heck::ToSnekCase; +``` + +Let's start by creating a simple function to take a single string, and convert it to snake case. First, the trait `ToSnekCase` needs to be imported so that the method `to_snek_case()` is available to `&str`. + +```{extendrsrc} +use heck::ToSnekCase; + +#[extendr] +fn to_snek_case(x: &str) -> String { + x.to_snek_case() +} +``` + +Simple enough, right? Let's give it a shot. To make it accessible from your R session, it needs to be included in your `extendr_module! {}` macro. + +```rust +extendr_module! { + mod heck; + fn to_snek_case; +} +``` + +From your R session, run `rextendr::document()` followed by `devtools::load_all()` to make the function available. We'll skip these step from now on, but be sure to remember it! + +```{r} +to_snek_case("MakeMe-Snake case") +``` + +Rarely is it useful to run a function on just a scalar character value. Rust, though, works with scalars by default and adding vectorization is another step. + +```{r, error = TRUE} +to_snek_case(c("DontStep", "on-Snek")) +``` + +Providing a character vector causes an error. So how do you go about vectorizing? + +## vectorizing snek case conversion + +To vectorize this function, you need to be apply the conversion to each element in a character vector. The extendr wrapper struct for a character vector is called `Strings`. To take in a character vector and also return one, the function signature should look like this: + +```rust +#[extendr] +fn to_snek_case(x: Strings) -> Strings { +} +``` + +This says there is an argument `x` which must be a character vector and this function must also `->` return the `Strings` (a character vector). + +To iterate through this you can use the `.into_iter()` method on the character vector. + +```rust +#[extendr] +fn to_snek_case(x: Strings) -> Strings { + x + .into_iter() + // the rest of the function +} +``` + +Iterators have a method called `.map()` (yes, just like `purrr::map()`). It lets you apply a closure (an anonymous function) to each element of the iterator. In this case, each element is an [`Rstr`](https://extendr.github.io/extendr/extendr_api/wrapper/rstr/struct.Rstr.html). The `Rstr` has a method `.as_str()` which will return a string slice `&str`. You can take this slice and pass it on to `.to_snek_case()`. After having mapped over each element, the results are `.collect()`ed into another `Strings`. + + +```{extendrsrc preamble = "use_heck"} +#[extendr] +fn to_snek_case(x: Strings) -> Strings { + x + .into_iter() + .map(|xi| { + xi.as_str().to_snek_case() + }) + .collect::() +} +``` + + +This new version of the function can be used in a vectorized manner: + +```{r} +to_snek_case(c("DontStep", "on-Snek")) +``` + +But can it handle a missing value out of the box? + +```{r} +to_snek_case(c("DontStep", NA_character_, "on-Snek")) +``` + +Well, sort of. The `as_str()` method when used on a missing value will return `"NA"` which is not in a user's best interest. + + +## handling missing values + +Instead of returning `"na"`, it would be better to return an _actual_ missing value. Those can be created each scalar's `na()` method e.g. `Rstr::na()`. + +You can modify the `.map()` statement to check if an `NA` is present, and, if so, return an `NA` value. To perform this check, use the `is_na()` method which returns a `bool` which is either `true` or `false`. The result can be [`match`ed](https://doc.rust-lang.org/book/ch06-02-match.html). When it is missing, the match arm returns the `NA` scalar value. When it is not missing, the `Rstr` is converted to snek case. However, since the `true` arm is an `Rstr` the other `false` arm must _also_ be an `Rstr`. To accomplish this use the `Rstr::from()` method. + +```{extendrsrc preamble = "use_heck", profile="release"} +#[extendr] +fn to_snek_case(x: Strings) -> Strings { + x.into_iter() + .map(|xi| match xi.is_na() { + true => Rstr::na(), + false => Rstr::from(xi.as_str().to_snek_case()), + }) + .collect::() +} +``` + +This function can now handle missing values! + +```{r} +to_snek_case(c("DontStep", NA_character_, "on-Snek")) +``` + +## automating other methods with a macro! + +There are traits for the other case conversions such as `ToKebabCase`, `ToPascalCase`, `ToShoutyKebabCase` and others. The each have a similar method name: `.to_kebab_case()`, `to_pascal_case()`, `.to_shouty_kebab_case()`. You can either choose to copy the above and change the method call multiple times, _or_ use a macro as a form of code generation. + +A macro allows you to generate code in a short hand manner. This macro take an identifier which has a placeholder called `$fn_name`: `$fn_name:ident`. + +```rust +macro_rules! make_heck_fn { + ($fn_name:ident) => { + #[extendr] + /// @export + fn $fn_name(x: Strings) -> Strings { + x.into_iter() + .map(|xi| match xi.is_na() { + true => Rstr::na(), + false => Rstr::from(xi.as_str().$fn_name()), + }) + .collect::() + } + }; +} +``` + +The `$fn_name` placeholder is put as the function name definition which is the same as the method name. To use this macro to generate the rest of the functions the other traits need to be imported. + +```{extendrsrc heck_traits} +use heck::{ + ToKebabCase, ToShoutyKebabCase, + ToSnekCase, ToShoutySnakeCase, + ToPascalCase, ToUpperCamelCase, + ToTrainCase, ToTitleCase, +}; +``` + +With the traits in scope, the macro can be invoked to generate the other functions. + +```rust +make_heck_fn!(to_snek_case); +make_heck_fn!(to_shouty_snake_case); +make_heck_fn!(to_kebab_case); +make_heck_fn!(to_shouty_kebab_case); +make_heck_fn!(to_pascal_case); +make_heck_fn!(to_upper_camel_case); +make_heck_fn!(to_train_case); +make_heck_fn!(to_title_case); +``` + +Note that each of these functions should be added to the `extendr_module! {}` macro in order for them to be available from R. + +```{extendrsrc preamble = "heck_traits", include = FALSE} +#[extendr] +fn to_shouty_kebab_case(x: Strings) -> Strings { + x.into_iter() + .map(|xi| match xi.is_na() { + true => Rstr::na(), + false => Rstr::from(xi.as_str().to_shouty_kebab_case()), + }) + .collect::() +} +``` + +Test it out with the `to_shouty_kebab_case()` function! + +```{r} +to_shouty_kebab_case("lorem:IpsumDolor__sit^amet") +``` + +And with that, you've created an R package that provides case conversion using heck and with very little code! + + +## bench marking with `{snakecase}` + +To illustrate the performance gains from using a vectorized Rust funciton, a `bench::mark()` is created between `to_snek_case()` and `snakecase::to_snake_case()`. + +```{r include=FALSE} +rextendr::rust_source(code = r"( +use heck::ToSnekCase; + +#[extendr] +fn to_snek_case(x: Strings) -> Strings { + x.into_iter() + .map(|xi| match xi.is_na() { + true => Rstr::na(), + false => Rstr::from(xi.as_str().to_snek_case()), + }) + .collect::() +} +)", dependencies = list("heck" = "*"), profile = "release") +``` + +The bench mark will use 5000 randomly generated lorem ipsum sentences. + +```{r, warning = FALSE} +x <- unlist(lorem::ipsum(5000, 1, 25)) + +head(x) + +bench::mark( + rust = to_snek_case(x), + snakecase = snakecase::to_snake_case(x) +) +``` + + + +## The whole thing + +In just 42 lines of code (empty lines included), you can create a very performant R package! + +```rust +use extendr_api::prelude::*; + +use heck::{ + ToKebabCase, ToPascalCase, ToShoutyKebabCase, ToShoutySnakeCase, ToSnekCase, ToTitleCase, + ToTrainCase, ToUpperCamelCase, +}; + +macro_rules! make_heck_fn { + ($fn_name:ident) => { + #[extendr] + /// @export + fn $fn_name(x: Strings) -> Strings { + x.into_iter() + .map(|xi| match xi.is_na() { + true => Rstr::na(), + false => Rstr::from(xi.as_str().$fn_name()), + }) + .collect::() + } + }; +} + +make_heck_fn!(to_snek_case); +make_heck_fn!(to_shouty_snake_case); +make_heck_fn!(to_kebab_case); +make_heck_fn!(to_shouty_kebab_case); +make_heck_fn!(to_pascal_case); +make_heck_fn!(to_upper_camel_case); +make_heck_fn!(to_train_case); +make_heck_fn!(to_title_case); + +extendr_module! { + mod heck; + fn to_snek_case; + fn to_shouty_snake_case; + fn to_kebab_case; + fn to_shouty_kebab_case; + fn to_pascal_case; + fn to_upper_camel_case; + fn to_title_case; + fn to_train_case; +} +``` \ No newline at end of file diff --git a/user-guide/error-handling/basic-error-handling.qmd b/user-guide/error-handling/basic-error-handling.qmd new file mode 100644 index 0000000..4f2d493 --- /dev/null +++ b/user-guide/error-handling/basic-error-handling.qmd @@ -0,0 +1,3 @@ +--- +title: "Basic Error Handling" +--- diff --git a/user-guide/index.qmd b/user-guide/index.qmd new file mode 100644 index 0000000..4074cc1 --- /dev/null +++ b/user-guide/index.qmd @@ -0,0 +1,12 @@ +--- +title: "User Guide" +subtitle: "A comprehensive guide to using extendr." +--- + + + + + + + + \ No newline at end of file diff --git a/user-guide/type-mapping/characters.qmd b/user-guide/type-mapping/characters.qmd new file mode 100644 index 0000000..01df022 --- /dev/null +++ b/user-guide/type-mapping/characters.qmd @@ -0,0 +1,3 @@ +--- +title: "Character Strings" +--- diff --git a/user-guide/type-mapping/missing-values.qmd b/user-guide/type-mapping/missing-values.qmd new file mode 100644 index 0000000..4a24cdd --- /dev/null +++ b/user-guide/type-mapping/missing-values.qmd @@ -0,0 +1,3 @@ +--- +title: "Missing Values" +--- diff --git a/user-guide/type-mapping/scalars.qmd b/user-guide/type-mapping/scalars.qmd new file mode 100644 index 0000000..c5417b6 --- /dev/null +++ b/user-guide/type-mapping/scalars.qmd @@ -0,0 +1,184 @@ +--- +title: "Scalar Type Mapping" +--- + +```{r} +#| echo: false + +library(rextendr) + +``` + +This tutorial demonstrates some of the basics of passing scalar data types back +and forth between Rust and R. We'll start with simple examples using explicit +Rust types but then move on to showing their extendr alternatives. Why does +extendr have its own data types? For a number of reasons, of course, but the +most important reason is probably that Rust types do not allow for missing +values, so no `NA`, `NaN`, `NULL`, or what have you. Fortunately, extendr types +will handle missing values for you. For this reason, **it is strongly +recommended that you work with the extendr types whenever possible.** + +## Scalar types + +A scalar type consists of a single value, and it can *only* consist of a single +value, whether that value is a single character string, integer, or logical. As +it happens, R doesn't have a way of representing a scalar value. That's because +everything is a vector in R, and vectors can have any arbitrary length you want. +So, the closest thing to a scalar you will ever encounter in R is a vector that +just so happens to have a length of one. In Rust, however, scalars are the +building blocks of everything, and they come in a bewildering variety, at least +for the traditional R user. Consider, for example, integers. R has just one way +to represent this type of numeric value. Rust, on the other hand, has twelve! + +The table below shows the most common R "scalar" types, along with their Rust +and extendr equivalents. + +| R type | extendr type | Rust type | +|----------------|--------------|----------------| +| `integer(1)` | `Rint` | `i32` | +| `double(1)` | `Rfloat` | `f64` | +| `logical(1)` | `Rbool` | `bool` | +| `complex(1)` | `Rcplx` | `Complex` | +| `character(1)` | `Rstr` | `String` | + +To learn more about Rust types, see [section 3.2 of The +Book](https://doc.rust-lang.org/book/ch03-02-data-types.html). + +## Sharing scalars + +To see how scalars get passed back and forth between Rust and R, we'll first +explore Rust's `f64` value which is a 64-bit float. This is equivalent to R's +`double(1)`. We'll write a very simple Rust function that prints the value of +the input and does not return anything. + +```{extendrsrc} +#[extendr] +fn scalar_double(x: f64) { + rprintln!("The value of x is {x}"); +} +``` + +Through the magic of extendr, we can now call this function in R and pass it a +single double value. + +```{r} +scalar_double(4.2) +``` + +There are several things to note about this example. First, in Rust, `x: f64` +tells us that the type of `x` being passed to the function (`fn`) is a single +double vector or "float" value. Second, `rprintln!("{}", x);` is an extendr +macro that makes it easier to print information from Rust to the console in R. +R users will perhaps notice that the syntax is vaguely `{glue}`-like in that the +value of `x` is inserted into the curly brackets. Finally, if you are not +working inside of an extendr R package, you can create the `scalar_double()` +function locally using `rextendr::rust_function()`. + +``` r +rextendr::rust_function(" +fn scalar_double(x: f64) { + rprintln!("The value of x is {x}"); +} +") +``` + +Now, what if, rather than printing the value of `x` to the R console, we wanted +instead to return that value to R? To do that, we just need to let Rust know +what type is being returned by our function. This is done with the `-> type` +notation. The extendr crate understands this notation and knows how to handle +the scalar `f64` type returned by the Rust function and pass it to R as double. + +```{extendrsrc} +#[extendr] +fn return_scalar_double(x: f64) -> f64 { + x +} +``` + +```{r} +x <- return_scalar_double(4.2) + +typeof(x) + +x + 1.0 + +``` + +## Missing values + +As noted above, Rust does not allow a scalar type to have a missing value, so +you cannot simply pass a missing value like `NA` to Rust and expect it to just +work. Here is a demonstration of this issue using a simple function which adds +1.0 to `x`. + +```{extendrsrc} +#[extendr] +fn plus_one(x: f64) -> f64 { + x + 1.0 +} +``` + +You will notice that this function expects `x` to be `f64`, not a missing value. +Passing a missing value from R to this Rust function will, therefore, result in +an error. + +```{r} +#| error: true +plus_one(NA_real_) +``` + +Fortunately, the extendr types are `NA`-aware, so you can, for instance, use +extendr's `Rfloat` in place of `f64` to handle missing values without error. +Below, you will see that we have done this for the function `plus_one()`. + +```{extendrsrc} +#[extendr] +fn plus_one(x: Rfloat) -> Rfloat { + x + 1.0 +} +``` + +```{r} +plus_one(NA_real_) + +plus_one(4.2) +``` + +## Additional examples + +Here are some additional examples showing how to pass scalars to Rust and return them +to R using Rust scalar types. + +```{extendrsrc} +#[extendr] +fn scalar_integer(x: i32) -> i32 { x } + +#[extendr] +fn scalar_logical(x: bool) -> bool { x } +``` + +```{r} +scalar_integer(4L) + +scalar_logical(TRUE) +``` + +And here are the same examples with extendr scalar types. + +```{extendrsrc} +#[extendr] +fn scalar_integer(x: Rint) -> Rint { x } + +#[extendr] +fn scalar_logical(x: Rbool) -> Rbool { x } +``` + +```{r} +scalar_integer(4L) + +scalar_logical(TRUE) +``` + +Did you notice that we didn't give an example with character strings? Yeah, well, +there's a good reason for that. You can find out what that is by heading over to +the tutorial on [Handling Strings](user-guide/type-mapping/characters.qmd). \ No newline at end of file diff --git a/user-guide/type-mapping/vectors.qmd b/user-guide/type-mapping/vectors.qmd new file mode 100644 index 0000000..78fde3a --- /dev/null +++ b/user-guide/type-mapping/vectors.qmd @@ -0,0 +1,120 @@ +--- +title: "Vector Type Mapping" +--- + +```{r} +#| echo: false + +library(rextendr) + +``` + +## Vector Type Mapping with Rust Types + +What happens if we try to pass more than one value to `scalar_double()`? + +```{r} +#| error: true + +scalar_double(c(4.2, 1.3, 2.5)) + +``` + +It errors because the function expects a scalar of the `f64` type, not a vector +of `f64`. + +In this section, we show you how to pass Rust vectors between R and Rust. + +::: callout-important +While using a Rust vector is possible in some cases, it is strongly not +recommended. Instead, extendr types should be used as they provide access +directly to R objectes. Whereas using Rust vectors requires additional +allocations. +::: + +The syntax is basically the same as with scalars, with just some minor changes. +We'll use doubles again to demonstrate this. + +For reference, below are the type of Rust vectors that can be utilized with +extendr. + +| R type | extendr type | Rust type | +|---------------|--------------|---------------------| +| `integer()` | `Integers` | `Vec` | +| `double()` | `Doubles` | `Vec` | +| `complex()` | `Complexes` | `Vec>` | +| `character()` | `Strings` | `Vec` | +| `raw()` | `Raw` | `&[u8]` | +| `logical()` | `Logicals` | | +| `list()` | `List` | | + +::: callout-note +You might have anticipated `Vec` to be a supported Rust vector type. This +is not possible because in R, logical vectors do not contain *only* `true` and +`false` like Rust's bool type. They also can be an `NA` value which has no +corresponding representation in Rust. +::: + +Below defines Rust function which takes in a vector of `f64` values and prints +them out. + +```{extendrsrc} +#[extendr] +fn vector_double(x: Vec) { + rprintln!("The values of x are {x:?}"); +} +``` + +That function can be called from R which prints the Debug format of the vector. + +::: callout-tip +Rust's vector do not implement the +[Display](https://doc.rust-lang.org/std/fmt/trait.Display.html) trait so the +debug format (`:?`) is used. +::: + +```{r} +vector_double(c(4.2, 1.3, 2.5)) +``` + +Returning values using Rust follows the same rules as R. You do not need to +explicitly return a value as long as the last item in an expression is not +followed by a `;`. + +```{extendrsrc} +#[extendr] +fn vector_double(x: Vec) -> Vec { + x +} +``` + +Calling the function returns the input as a double vector + +```{r} +x <- vector_double(c(4.2, 1.3, 2.5)) +typeof(x) +x + 1 +``` + +### Additional examples + +These same principles can be extended to other supported vector types such as +`Vec` and `Vec`. + +```{extendrsrc} +#[extendr] +fn vector_integer(x: Vec) -> Vec { + x +} + +#[extendr] +fn vector_character(x: Vec) -> Vec { + x +} +``` + +```{r} +vector_integer(c(4L, 6L, 8L)) + +vector_character(c("Hello world!", "Hello extendr!", "Hello R!")) +``` \ No newline at end of file