├── .cargo └── config.toml ├── .gitignore ├── .rustfmt.toml ├── .vscode └── settings.json ├── Cargo.toml ├── LICENSE-APACHE ├── LICENSE-MIT ├── Makefile.toml ├── README.md ├── docs ├── CHANGELOG.md ├── CONTRIBUTING.md ├── Development.md ├── Maintenance.md ├── QuickStart.md └── WhyRust.md ├── scripts ├── BuildPaclet.wls └── re_build_WLLibraryLink.xml ├── wolfram-library-link-sys ├── .gitignore ├── .rustfmt.toml ├── Cargo.toml ├── README.md ├── build.rs ├── generated │ ├── 12.1.0 │ │ └── MacOSX-x86-64 │ │ │ └── LibraryLink_bindings.rs │ ├── 12.1.1 │ │ └── MacOSX-x86-64 │ │ │ └── LibraryLink_bindings.rs │ ├── 12.2.0 │ │ └── MacOSX-x86-64 │ │ │ └── LibraryLink_bindings.rs │ ├── 12.3.0 │ │ └── MacOSX-x86-64 │ │ │ └── LibraryLink_bindings.rs │ ├── 13.0.0 │ │ └── MacOSX-x86-64 │ │ │ └── LibraryLink_bindings.rs │ ├── 13.0.1 │ │ ├── Linux-ARM64 │ │ │ └── LibraryLink_bindings.rs │ │ ├── Linux-x86-64 │ │ │ └── LibraryLink_bindings.rs │ │ ├── MacOSX-ARM64 │ │ │ └── LibraryLink_bindings.rs │ │ ├── MacOSX-x86-64 │ │ │ └── LibraryLink_bindings.rs │ │ └── Windows-x86-64 │ │ │ └── LibraryLink_bindings.rs │ └── 13.2.0 │ │ ├── MacOSX-ARM64 │ │ └── LibraryLink_bindings.rs │ │ └── MacOSX-x86-64 │ │ └── LibraryLink_bindings.rs └── src │ └── lib.rs ├── wolfram-library-link ├── Cargo.toml ├── RustLink │ ├── Documentation │ │ └── English │ │ │ └── Tutorials │ │ │ └── GettingStarted.nb │ ├── Examples │ │ ├── Aborts.wlt │ │ ├── AsyncExamples.wlt │ │ ├── BasicTypes.wlt │ │ ├── DataStore.wlt │ │ ├── Docs │ │ │ ├── Convert │ │ │ │ ├── ManualWSTP.wlt │ │ │ │ └── UsingExpr.wlt │ │ │ └── EvaluateWolframCodeFromRust │ │ │ │ └── GenerateMessage.wlt │ │ ├── Expressions.wlt │ │ ├── ManagedExpressions.wlt │ │ ├── NumericArrays.wlt │ │ ├── RawFunctions.wlt │ │ └── WSTP.wlt │ ├── LibraryResources │ │ └── .include_in_git │ ├── PacletInfo.m │ └── Tests │ │ ├── DataStore.wlt │ │ ├── Images.wlt │ │ ├── NativeArgs.wlt │ │ ├── NumericArrayConversions.wlt │ │ ├── ShareCounts.wlt │ │ ├── Threading.wlt │ │ └── WSTP.wlt ├── docs │ ├── Overview.md │ └── README.md ├── examples │ ├── aborts.rs │ ├── async │ │ ├── async_file_watcher.rs │ │ └── async_file_watcher_raw.rs │ ├── basic_types.rs │ ├── data_store.rs │ ├── docs │ │ ├── README.md │ │ ├── convert │ │ │ ├── manual_wstp.rs │ │ │ └── using_expr.rs │ │ ├── evaluate_wolfram_code_from_rust │ │ │ └── generate_message.rs │ │ └── main.rs │ ├── exprs │ │ ├── basic_expressions.rs │ │ └── managed.rs │ ├── numeric_arrays.rs │ ├── raw │ │ ├── raw_librarylink_function.rs │ │ └── raw_wstp_function.rs │ ├── tests │ │ ├── README.md │ │ ├── main.rs │ │ ├── test_data_store.rs │ │ ├── test_images.rs │ │ ├── test_native_args.rs │ │ ├── test_numeric_array_conversions.rs │ │ ├── test_share_counts.rs │ │ ├── test_threading.rs │ │ └── test_wstp.rs │ └── wstp.rs ├── src │ ├── args.rs │ ├── async_tasks.rs │ ├── catch_panic.rs │ ├── data_store.rs │ ├── docs.rs │ ├── docs │ │ ├── converting_between_rust_and_wolfram_types.rs │ │ └── evaluate_wolfram_code_from_rust.rs │ ├── image.rs │ ├── lib.rs │ ├── library_data.rs │ ├── macro_utils.rs │ ├── managed.rs │ ├── numeric_array.rs │ └── rtl.rs └── wolfram-library-link-macros │ ├── Cargo.toml │ └── src │ ├── export.rs │ └── lib.rs └── xtask ├── Cargo.toml └── src └── main.rs /.cargo/config.toml: -------------------------------------------------------------------------------- 1 | [alias] 2 | xtask = "run --package xtask --" 3 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | **/*.rs.bk 3 | Cargo.lock 4 | 5 | /build -------------------------------------------------------------------------------- /.rustfmt.toml: -------------------------------------------------------------------------------- 1 | max_width = 90 2 | comment_width = 90 3 | match_block_trailing_comma = true 4 | blank_lines_upper_bound = 3 5 | merge_derives = false 6 | overflow_delimited_expr = true 7 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "editor.formatOnSave": true, 3 | "editor.formatOnSaveMode": "file" 4 | } 5 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [workspace] 2 | resolver = "2" 3 | 4 | members = [ 5 | "wolfram-library-link", 6 | "wolfram-library-link-sys", 7 | "xtask" 8 | ] -------------------------------------------------------------------------------- /LICENSE-APACHE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS -------------------------------------------------------------------------------- /LICENSE-MIT: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 Wolfram Research Inc. 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /Makefile.toml: -------------------------------------------------------------------------------- 1 | [config] 2 | default_to_workspace = false 3 | 4 | #======================================= 5 | # Task definitions 6 | #======================================= 7 | 8 | #------------------ 9 | # Development tasks 10 | #------------------ 11 | 12 | [tasks.paclet] 13 | dependencies = ["clean-library-resources", "build-library-resources"] 14 | script = { file = "./scripts/BuildPaclet.wls"} 15 | 16 | #-------------------- 17 | # Building the paclet 18 | #-------------------- 19 | 20 | [tasks.clean-library-resources] 21 | script = ''' 22 | rm -rf ./build/ 23 | ''' 24 | 25 | [tasks.build-library-resources] 26 | command = "cargo" 27 | args = ["build", "--examples"] 28 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # wolfram-library-link 2 | 3 | [![Crates.io](https://img.shields.io/crates/v/wolfram-library-link.svg)](https://crates.io/crates/wolfram-library-link) 4 | ![License](https://img.shields.io/crates/l/wolfram-library-link.svg) 5 | [![Documentation](https://docs.rs/wolfram-library-link/badge.svg)](https://docs.rs/wolfram-library-link) 6 | 7 |

8 | API Documentation 9 | | 10 | Changelog 11 | | 12 | Contributing 13 |

14 | 15 | Bindings to the Wolfram LibraryLink interface, making it possible to call Rust code 16 | from the Wolfram Language. 17 | 18 | This library is used for writing Rust programs that can be loaded by the Wolfram 19 | LibraryLink family of functions, specifically by 20 | [`LibraryFunctionLoad[]`][ref/LibraryFunctionLoad]. 21 | 22 | #### Features 23 | 24 | * Efficiently call Rust functions from Wolfram code. 25 | * Pass arbitrary Wolfram expressions to and from Rust code. 26 | * Evaluate Wolfram expressions from Rust code. 27 | * Respond to Wolfram [abort][interrupts] requests while in Rust code. 28 | * Safe API for the Wolfram Symbolic Transfer Protocol, using the [`wstp`][wstp] crate. 29 | 30 | Follow the [**Quick Start**](./docs/QuickStart.md) guide to begin using `wolfram-library-link`. 31 | 32 | See [**Why Rust?**](./docs/WhyRust.md) for an overview of some of the advantages Rust has 33 | when writing native code for use from the Wolfram Language: performance, memory and thread 34 | safety, high-level features, and more. 35 | 36 | [interrupts]: https://reference.wolfram.com/language/tutorial/InterruptingCalculations.html 37 | 38 | ## Quick Examples 39 | 40 | The examples in this section are written with two back-to-back code blocks. The first 41 | shows the Rust code, and the second shows the Wolfram Language code needed to load and use 42 | the related Rust function(s). 43 | 44 | #### Basic data types 45 | 46 | ```rust 47 | use wolfram_library_link::export; 48 | 49 | #[export] 50 | fn square(x: i64) -> i64 { 51 | x * x 52 | } 53 | ``` 54 | 55 | ```wolfram 56 | square = LibraryFunctionLoad["...", "square", {Integer}, Integer]; 57 | 58 | square[5] 59 | ``` 60 | 61 | See also: [`LibraryFunctionLoad`][ref/LibraryFunctionLoad] 62 | 63 | #### Efficient numeric arrays 64 | 65 | Create an array of a million integers in Wolfram Language and compute the total using 66 | Rust: 67 | 68 | ```rust 69 | use wolfram_library_link::{export, NumericArray}; 70 | 71 | #[export] 72 | fn total(array: &NumericArray) -> i64 { 73 | array.as_slice().into_iter().sum() 74 | } 75 | ``` 76 | 77 | ```wolfram 78 | total = LibraryFunctionLoad[ 79 | "...", 80 | "square", 81 | {LibraryDataType[NumericArray, "Integer64"]}, 82 | Integer 83 | ]; 84 | 85 | total[NumericArray[Range[1000000], "Integer64"]] 86 | ``` 87 | 88 | See also: [`NumericArray`][ref/NumericArray], [`LibraryDataType`][ref/LibraryDataType] 89 | 90 | ## Example Programs 91 | 92 | The [wolfram-library-link/examples](./wolfram-library-link/examples) subdirectory 93 | contains sample programs demonstrating features of the `wolfram-library-link` API. 94 | 95 | Rust code | Wolfram Language code | Demonstrates ... 96 | -----------------------------------------------------------------------------------|-----------------------------------------------------------------------------------------------------|------------------------------- 97 | [basic_types.rs](wolfram-library-link/examples/basic_types.rs) | [BasicTypes.wlt](wolfram-library-link/RustLink/Examples/BasicTypes.wlt) | how to write Rust *LibraryLink* functions utilizing the basic, native types that can be passed efficiently, like integers, floating-point real numbers, and strings. 98 | [numeric_arrays.rs](wolfram-library-link/examples/numeric_arrays.rs) | [NumericArrays.wlt](wolfram-library-link/RustLink/Examples/NumericArrays.wlt) | how the [`NumericArray`][NumericArray] data type can be used to efficiently pass large multi-dimensional arrays of uniform numeric data. 99 | [wstp.rs](wolfram-library-link/examples/wstp.rs) | [WSTP.wlt](wolfram-library-link/RustLink/Examples/WSTP.wlt) | how WSTP [`Link`][wstp::Link]s can be used to pass arbitrary expressions to and from LibraryLink functions. 100 | [aborts.rs](wolfram-library-link/examples/aborts.rs) | [Aborts.wlt](wolfram-library-link/RustLink/Examples/Aborts.wlt) | how Rust code can respond to Wolfram [abort requests][interrupts]. 101 | [async_file_watcher.rs](wolfram-library-link/examples/async/async_file_watcher.rs) | [AsyncExamples.wlt](wolfram-library-link/RustLink/Examples/AsyncExamples.wlt) | how Rust code can generate asynchronous events that trigger Wolfram evaluations to process the event. 102 | [managed.rs](wolfram-library-link/examples/exprs/managed.rs) | [ManagedExpressions.wlt](wolfram-library-link/RustLink/Examples/ManagedExpressions.wlt) | how the managed expression API can be used to free library data when a Wolfram expression is deallocated. 103 | [data_store.rs](wolfram-library-link/examples/data_store.rs) | [DataStore.wlt](wolfram-library-link/RustLink/Examples/DataStore.wlt) | how the [`DataStore`][DataStore] data type can be used to efficiently pass arbitrary expression-like heterogenous structures made up of native *LibraryLink* data types. 104 | 105 | [NumericArray]: https://docs.rs/wolfram-library-link/latest/wolfram_library_link/struct.NumericArray.html 106 | [wstp::Link]: https://docs.rs/wstp/latest/wstp/struct.Link.html 107 | [DataStore]: https://docs.rs/wolfram-library-link/latest/wolfram_library_link/struct.DataStore.html 108 | 109 | #### Raw functions 110 | 111 | These examples demonstrate how to write functions that use the "raw" low-level 112 | *LibraryLink* and WSTP interfaces, using the `extern "C"` ABI, the low-level `MArgument` 113 | and `WSLINK` types, and manual WSTP operations. 114 | 115 | Rust code | Wolfram Language code 116 | ---------------------------------------------------------------------|----------------- 117 | [raw_librarylink_function.rs](wolfram-library-link/examples/raw/raw_librarylink_function.rs) and [raw_wstp_function.rs](wolfram-library-link/examples/raw/raw_wstp_function.rs) | [RawFunctions.wlt](wolfram-library-link/RustLink/Examples/RawFunctions.wlt) 118 | 119 | #### Additional examples 120 | 121 | In addition to the polished high-level examples, the 122 | [wolfram-library-link/examples/tests/](wolfram-library-link/examples/tests/) directory 123 | contains test code for a more exhaustive range of functionality and behavior, and may be a 124 | useful additional reference. The [RustLink/Tests/](./wolfram-library-link/RustLink/Tests/) directory contains 125 | the Wolfram Language unit testing logic that loads and calls the test functions. 126 | 127 | [wstp]: https://crates.io/crates/wstp 128 | [wolfram-expr]: https://crates.io/crates/wolfram-expr 129 | [wolfram-app-discovery]: https://crates.io/crates/wolfram-app-discovery 130 | [library-link]: https://reference.wolfram.com/language/guide/LibraryLink.html 131 | 132 | [wad-configuration]: https://github.com/WolframResearch/wolfram-app-discovery-rs#configuration 133 | 134 | [ref/LibraryFunctionLoad]: https://reference.wolfram.com/language/ref/LibraryFunctionLoad.html 135 | [ref/LibraryDataType]: https://reference.wolfram.com/language/ref/LibraryDataType.html 136 | [ref/NumericArray]: https://reference.wolfram.com/language/ref/NumericArray.html 137 | 138 | ## Building `wolfram-library-link` 139 | 140 | `wolfram-library-link` depends on the [`wstp`][wstp] crate for bindings to the Wolfram 141 | Symbolic Transfer Protocol (WSTP). Building the `wstp` crate requires access to the 142 | WSTP SDK, which provides the WSTP static library. `wstp` uses [`wolfram-app-discovery`][wolfram-app-discovery] to 143 | locate a local installation of the Wolfram Language that contains a suitable copy of the 144 | WSTP SDK. If the WSTP SDK cannot be located, `wstp` will fail to build, and consequently, 145 | so will `wolfram-library-link`. 146 | 147 | If you have installed the Wolfram Language to a location unknown to `wolfram-app-discovery`, 148 | you may specify the installed location manually by setting the `WOLFRAM_APP_DIRECTORY` 149 | environment variable. See [Configuring wolfram-app-discovery][wad-configuration] for details. 150 | 151 | ## Related Links 152 | 153 | #### Related crates 154 | 155 | * [`wstp`][wstp] — bindings to the Wolfram Symbolic Transfer Protocol, used for passing 156 | arbitrary Wolfram expressions between programs. 157 | * [`wolfram-expr`][wolfram-expr] — native Rust representation of Wolfram Language 158 | expressions. 159 | * [`wolfram-app-discovery`][wolfram-app-discovery] — utility for locating local 160 | installations of Wolfram applications and the Wolfram Language. 161 | 162 | #### Related documentation 163 | 164 | * [*Wolfram LibraryLink User Guide*](https://reference.wolfram.com/language/LibraryLink/tutorial/Overview.html) 165 | * [*Introducing C++ and the Wolfram Language with LibraryLinkUtilities*](https://community.wolfram.com/groups/-/m/t/2133603), a C++ wrapper around the *LibraryLink* API. 166 | 167 | ## License 168 | 169 | Licensed under either of 170 | 171 | * Apache License, Version 2.0 172 | ([LICENSE-APACHE](LICENSE-APACHE) or http://www.apache.org/licenses/LICENSE-2.0) 173 | * MIT license 174 | ([LICENSE-MIT](LICENSE-MIT) or http://opensource.org/licenses/MIT) 175 | 176 | at your option. 177 | 178 | Note: Licensing of the WSTP library linked by the [wstp][wstp] crate is covered by the 179 | terms of the 180 | [MathLink License Agreement](https://www.wolfram.com/legal/agreements/mathlink.html). 181 | 182 | ## Contribution 183 | 184 | Unless you explicitly state otherwise, any contribution intentionally submitted 185 | for inclusion in the work by you, as defined in the Apache-2.0 license, shall be 186 | dual licensed as above, without any additional terms or conditions. 187 | 188 | See [CONTRIBUTING.md](./docs/CONTRIBUTING.md) for more information. 189 | 190 | ### Developer Notes 191 | 192 | See [**Development.md**](./docs/Development.md) for instructions on how to perform common 193 | development tasks when contributing to the `wolfram-library-link` crate. 194 | 195 | See [**Maintenance.md**](./docs/Maintenance.md) for instructions on how to keep 196 | `wolfram-library-link` up to date as new versions of the Wolfram Language are released. 197 | -------------------------------------------------------------------------------- /docs/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | All notable changes to this project will be documented in this file. 4 | 5 | The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), 6 | and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). 7 | 8 | ## [Unreleased] 9 | 10 | 11 | ## [0.2.10] – 2023-08-28 12 | 13 | ### Changed 14 | 15 | * Always use pre-generated LibraryLink bindings. ([#57]) 16 | 17 | Previously, wolfram-library-link-sys would use bindgen to generate bindings at 18 | compile time. Now, the bindings are pre-generated and built-in to 19 | wolfram-library-link-sys, without any loss in functionality. 20 | 21 | The build dependency on bindgen has been removed, simplifying the dependency 22 | tree and reducing compile times. 23 | 24 | When wolfram-library-link occasionally updates to support a new minimum 25 | LibraryLink version, the bindings will need to be regenerated as described in 26 | [docs/Maintenance.md](./Maintenance.md). 27 | 28 | * Update wstp dependency to v0.2.8, which also replaces build-time use of 29 | bindgen with pre-generated bindings. ([#59]) 30 | 31 | * *Developer:* Replace custom RunTests.wls script with instructions on how to 32 | use the slightly more standardized `wolfram-cli paclet test` tool. ([#58]) 33 | 34 | ### Fixed 35 | 36 | * Fixed build failure caused by assumption that `c_char` is `i8` on every 37 | platform, which is not true. 38 | 39 | 40 | 41 | ## [0.2.9] – 2023-02-03 42 | 43 | ### Added 44 | 45 | * Add logging support to `wolfram-library-link-sys/build.rs`. ([#54]) 46 | 47 | [wolfram-app-discovery v0.4.3](https://github.com/WolframResearch/wolfram-app-discovery-rs/blob/master/docs/CHANGELOG.md#043--2023-02-03) 48 | added support for logging via the Rust [`log`](https://crates.io/crates/log) 49 | logging facade library. `wolfram-library-link-sys/build.rs` uses 50 | wolfram-app-discovery to find `WolframLibrary.h`. 51 | 52 | Logging messages from `wolfram-library-link-sys/build.rs` can now be enabled 53 | by setting the `RUST_LOG` environment to an appropriate value, as documented 54 | in the [`env_logger`](https://docs.rs/env_logger) crate documentation. This 55 | can help debug discovery errors that occur during a build. 56 | 57 | ### Changed 58 | 59 | * Reduce minimal configurable set of dependencies by adding new cargo features. 60 | ([#53]) 61 | 62 | This release adds two new cargo features: 63 | 64 | * `panic-failure-backtraces` 65 | * `automate-function-loading-boilerplate` 66 | 67 | which are enabled by default. 68 | 69 | These features are used to control whether the following (now optional) 70 | dependencies are enabled: 71 | 72 | * `backtrace` 73 | * `inventory` 74 | * `process_path` 75 | 76 | Making these dependnecies optional reduces the minimal possible set of 77 | overall dependencies in projects that use wolfram-library-link. 78 | 79 | 80 | 81 | ## [0.2.8] – 2023-02-01 82 | 83 | ### Changed 84 | 85 | * Update `wstp` and `wolfram-app-discovery` dependencies so that 86 | `wolfram-app-discovery` v0.4.1 is being used everywhere, which has improved 87 | Linux support, and fixes several bugs. ([#51]) 88 | 89 | 90 | 91 | ## [0.2.7] – 2022-09-19 92 | 93 | ### Changed 94 | 95 | * Update `wolfram-app-discovery` dependency from v0.2.1 to v0.3.0, to take 96 | advantage of the improved flexibility of the new API functions tailored for 97 | use in build scripts. ([#49]) 98 | 99 | 100 | 101 | ## [0.2.6] – 2022-08-28 102 | 103 | ### Fixed 104 | 105 | * Fixed `Failure["RustPanic", ..]` messages not being returned from 106 | `#[export(wstp)]` functions when a panic occurs after partial results had 107 | begun being written to the WSTP link. ([#46]) 108 | 109 | ### Changed 110 | 111 | * Clarified documentation problem 112 | [described in comment on #44](https://github.com/WolframResearch/wolfram-library-link-rs/issues/44#issuecomment-1153244113), 113 | the documentation for `exported_library_functions_association()` was unclear about 114 | when and how to use the `library` parameter. ([#47]) 115 | 116 | 117 | 118 | ## [0.2.5] – 2022-06-11 119 | 120 | ### Fixed 121 | 122 | * Fixed [issue #29](https://github.com/WolframResearch/wolfram-library-link-rs/issues/29), a compilation failure on Windows. ([#41], [#42]) 123 | 124 | 125 | 126 | ## [0.2.4] – 2022-05-13 127 | 128 | ### Added 129 | 130 | * Added a new 'How To' page describing how to perform Wolfram evaluations from Rust 131 | library functions. ([#38], [#39]) 132 | 133 | * [How To: Evaluate Wolfram code from Rust](https://docs.rs/wolfram-library-link/0.2.4/wolfram_library_link/docs/evaluate_wolfram_code_from_rust/index.html) 134 | 135 | 136 | 137 | ## [0.2.3] – 2022-03-29 138 | 139 | ### Fixed 140 | 141 | * Fixed docs.rs build failure in v0.2.2, caused by a `#[doc = include_str!(..)]` that 142 | fails on case-sensitive targets like the docs.rs Linux build host. ([#34]) 143 | 144 | 145 | 146 | ## [0.2.2] – 2022-03-28 147 | 148 | ### Added 149 | 150 | * Added a new [`wolfram_library_link::docs`](https://docs.rs/wolfram-library-link/0.2.3/wolfram_library_link/docs/index.html) 151 | module, and an initial 'How To' documentation page describing how to convert between 152 | Rust types and Wolfram expressions. ([#31]) 153 | 154 | * [How To: Convert Between Rust and Wolfram Types](https://docs.rs/wolfram-library-link/0.2.3/wolfram_library_link/docs/converting_between_rust_and_wolfram_types/index.html) 155 | 156 | 157 | 158 | ## [0.2.1] – 2022-03-09 159 | 160 | ### Added 161 | 162 | * Added [`exported_library_functions_association()`](https://docs.rs/wolfram-library-link/0.2.1/wolfram_library_link/fn.exported_library_functions_association.html). 163 | ([#26]) 164 | 165 | This function returns an [`Expr`](https://docs.rs/wolfram-expr/0.1.1/wolfram_expr/struct.Expr.html) 166 | containing an Association of the form `<| name_?StringQ -> func_ |>`, with an entry for 167 | each library function exported using `#[export(..)]`. 168 | 169 | Arguments that are applied to `func` will be used to call the compiled library function. 170 | 171 | * Added `#[export(hidden)]` annotation. 172 | 173 | Exported functions with the `hidden` annotation will not be included in the 174 | Association returned by `exported_library_functions_association()`. 175 | 176 | `exported_library_functions_association()` and `#[export(hidden)]` are alternatives to 177 | the `generate_loader![]` macro. The `generate_loader![]` macro is convenient, but I think 178 | it hides too many details about how it works. It's too much magic. 179 | 180 | Together, these two new features can be used by the library author to define a loader 181 | function for their own library, which would typically look like: 182 | 183 | ```rust 184 | use wolfram_library_link::{self as wll, export, expr::Expr}; 185 | 186 | #[export(wstp, hidden)] 187 | fn load_my_lib_funcs(_args: Vec) -> Expr { 188 | return wll::exported_library_functions_association(None); 189 | } 190 | 191 | #[export] 192 | fn square(x: i64) -> i64 { 193 | x * x 194 | } 195 | ``` 196 | 197 | and which could be used from the Wolfram Language by evaluating: 198 | 199 | ```wolfram 200 | loadLibraryFunctions = LibraryFunctionLoad[ 201 | "", 202 | "load_my_lib_funcs", 203 | LinkObject, 204 | LinkObject 205 | ]; 206 | 207 | $functions = loadLibraryFunctions[]; 208 | ``` 209 | 210 | Then, any function exported from the library could be called by accessing the named values 211 | in `$functions`: 212 | 213 | ```wolfram 214 | (* Call the `square()` function exported by this library. *) 215 | $functions["square"][5] 216 | ``` 217 | 218 | 219 | 220 | ## [0.2.0] – 2022-03-07 221 | 222 | ### Added 223 | 224 | * Added new `#[export(..)]` attribute macro. ([#23]) 225 | 226 | Export a native function: 227 | 228 | ```rust 229 | use wolfram_library_link::export; 230 | 231 | #[export] 232 | fn square(x: i64) -> i64 { 233 | x * x 234 | } 235 | ``` 236 | 237 | Export a WSTP function: 238 | 239 | ```rust 240 | use wolfram_library_link::{export, wstp::Link}; 241 | 242 | #[export(wstp)] 243 | fn total(link: &mut Link) { 244 | let arg_count = link.test_head("List").unwrap(); 245 | 246 | let mut total = 0; 247 | 248 | for _ in 0..arg_count { 249 | total += link.get_i64().unwrap(); 250 | } 251 | 252 | link.put_i64(total).unwrap(); 253 | } 254 | ``` 255 | 256 | ### Changed 257 | 258 | * Changed `wolfram-library-link-sys` to generate the Rust bindings to `WolframLibrary.h` 259 | at compile time. ([#24]) 260 | 261 | This ensures that the `wolfram-library-link` and `wolfram-library-link-sys` crates can 262 | compile against the widest possible range of suppported Wolfram Language versions. 263 | 264 | ### Removed 265 | 266 | * Removed the `export![]` and `export_wstp![]` declarative macros. These have been 267 | replaced by `#[export(..)]`. ([#23]) 268 | 269 | 270 | 271 | ## [0.1.2] – 2022-02-08 272 | 273 | ### Fixed 274 | 275 | * Fix `wolfram-library-link-sys/build.rs` failure when building in the build 276 | environment, where no Wolfram applications are available to query. ([#17]) 277 | 278 | 279 | 280 | ## [0.1.1] – 2022-02-08 281 | 282 | ### Fixed 283 | 284 | * Update `wstp` dependency to fix build failures caused by earlier versions of 285 | `wstp-sys`. ([#16]) 286 | * Fix missing `"full"` feature needed by the `syn` dependency of 287 | `wolfram-library-link-macros`. ([#16]) 288 | 289 | 290 | 291 | ## [0.1.0] – 2022-02-08 292 | 293 | Initial release. `wolfram-library-link-sys` was the only crate published in this release, 294 | due to a [docs.rs build failure](https://docs.rs/crate/wolfram-library-link-sys/0.1.0) 295 | caused by bugs present in early versions of `wolfram-app-discovery` and `wstp-sys`. 296 | 297 | 298 | 299 | 300 | [#16]: https://github.com/WolframResearch/wolfram-library-link-rs/pull/16 301 | [#17]: https://github.com/WolframResearch/wolfram-library-link-rs/pull/17 302 | 303 | 304 | [#23]: https://github.com/WolframResearch/wolfram-library-link-rs/pull/23 305 | [#24]: https://github.com/WolframResearch/wolfram-library-link-rs/pull/24 306 | 307 | 308 | [#26]: https://github.com/WolframResearch/wolfram-library-link-rs/pull/26 309 | 310 | 311 | [#31]: https://github.com/WolframResearch/wolfram-library-link-rs/pull/31 312 | 313 | 314 | [#34]: https://github.com/WolframResearch/wolfram-library-link-rs/pull/34 315 | 316 | 317 | [#38]: https://github.com/WolframResearch/wolfram-library-link-rs/pull/38 318 | [#39]: https://github.com/WolframResearch/wolfram-library-link-rs/pull/39 319 | 320 | 321 | [#41]: https://github.com/WolframResearch/wolfram-library-link-rs/pull/41 322 | [#42]: https://github.com/WolframResearch/wolfram-library-link-rs/pull/42 323 | 324 | 325 | [#46]: https://github.com/WolframResearch/wolfram-library-link-rs/pull/46 326 | [#47]: https://github.com/WolframResearch/wolfram-library-link-rs/pull/47 327 | 328 | 329 | [#49]: https://github.com/WolframResearch/wolfram-library-link-rs/pull/49 330 | 331 | 332 | [#51]: https://github.com/WolframResearch/wolfram-library-link-rs/pull/51 333 | 334 | 335 | [#53]: https://github.com/WolframResearch/wolfram-library-link-rs/pull/53 336 | [#54]: https://github.com/WolframResearch/wolfram-library-link-rs/pull/54 337 | 338 | 339 | [#57]: https://github.com/WolframResearch/wolfram-library-link-rs/pull/57 340 | [#58]: https://github.com/WolframResearch/wolfram-library-link-rs/pull/58 341 | [#59]: https://github.com/WolframResearch/wolfram-library-link-rs/pull/59 342 | 343 | 344 | 345 | [Unreleased]: https://github.com/WolframResearch/wolfram-library-link-rs/compare/v0.2.10...HEAD 346 | 347 | [0.2.10]: https://github.com/WolframResearch/wolfram-library-link-rs/compare/v0.2.9...v0.2.10 348 | [0.2.9]: https://github.com/WolframResearch/wolfram-library-link-rs/compare/v0.2.8...v0.2.9 349 | [0.2.8]: https://github.com/WolframResearch/wolfram-library-link-rs/compare/v0.2.7...v0.2.8 350 | [0.2.7]: https://github.com/WolframResearch/wolfram-library-link-rs/compare/v0.2.6...v0.2.7 351 | [0.2.6]: https://github.com/WolframResearch/wolfram-library-link-rs/compare/v0.2.5...v0.2.6 352 | [0.2.5]: https://github.com/WolframResearch/wolfram-library-link-rs/compare/v0.2.4...v0.2.5 353 | [0.2.4]: https://github.com/WolframResearch/wolfram-library-link-rs/compare/v0.2.3...v0.2.4 354 | [0.2.3]: https://github.com/WolframResearch/wolfram-library-link-rs/compare/v0.2.2...v0.2.3 355 | [0.2.2]: https://github.com/WolframResearch/wolfram-library-link-rs/compare/v0.2.1...v0.2.2 356 | [0.2.1]: https://github.com/WolframResearch/wolfram-library-link-rs/compare/v0.2.0...v0.2.1 357 | [0.2.0]: https://github.com/WolframResearch/wolfram-library-link-rs/compare/v0.1.2...v0.2.0 358 | [0.1.2]: https://github.com/WolframResearch/wolfram-library-link-rs/compare/v0.1.1...v0.1.2 359 | [0.1.1]: https://github.com/WolframResearch/wolfram-library-link-rs/compare/v0.1.0...v0.1.1 360 | [0.1.0]: https://github.com/WolframResearch/wolfram-library-link-rs/releases/tag/v0.1.0 361 | -------------------------------------------------------------------------------- /docs/CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing to Wolfram® 2 | 3 | Thank you for taking the time to contribute to the 4 | [Wolfram Research](https://github.com/wolframresearch) repositories on GitHub. 5 | 6 | ## Licensing of Contributions 7 | 8 | By contributing to Wolfram, you agree and affirm that: 9 | 10 | > Wolfram may release your contribution under the terms of the [MIT license](https://opensource.org/licenses/MIT) 11 | > AND the [Apache 2.0 license](https://opensource.org/licenses/Apache-2.0); and 12 | 13 | > You have read and agreed to the [Developer Certificate of Origin](http://developercertificate.org/), version 1.1 or later. 14 | 15 | Please see [LICENSE](LICENSE) for licensing conditions pertaining 16 | to individual repositories. 17 | 18 | 19 | ## Bug reports 20 | 21 | ### Security Bugs 22 | 23 | Please **DO NOT** file a public issue regarding a security issue. 24 | Rather, send your report privately to security@wolfram.com. Security 25 | reports are appreciated and we will credit you for it. We do not offer 26 | a security bounty, but the forecast in your neighborhood will be cloudy 27 | with a chance of Wolfram schwag! 28 | 29 | ### General Bugs 30 | 31 | Please use the repository issues page to submit general bug issues. 32 | 33 | Please do not duplicate issues. 34 | 35 | Please do send a complete and well-written report to us. Note: **the 36 | thoroughness of your report will positively correlate with our ability to address it**. 37 | 38 | When reporting issues, always include: 39 | 40 | * Your version of *Mathematica*® or the Wolfram Language. 41 | * Your operating system. 42 | -------------------------------------------------------------------------------- /docs/Development.md: -------------------------------------------------------------------------------- 1 | # Development 2 | 3 | This document contains information useful to anyone wishing to modify the 4 | `wolfram-library-link` crate. 5 | 6 | ## Run the tests 7 | 8 | #### Build the `RustLink` crate 9 | 10 | Unlike many Rust crates that use the [`cargo-test`][cargo-test] subcommand to execute 11 | their tests, most `wolfram-library-link` tests are written in Wolfram, using the standard 12 | `MUnit` testing framework. This is necessary because the test functions are designed to be 13 | loaded via the Wolfram LibraryLink interface. 14 | 15 | The testing code is split between two locations: 16 | 17 | * The [wolfram-library-link/examples/tests/](../wolfram-library-link/examples/tests/) directory 18 | contains the Rust library test functions. 19 | * The [RustLink/Tests/](../RustLink/Tests/) directory contains the Wolfram unit testing 20 | logic that loads and calls the Rust test functions. 21 | 22 | The Rust tests are written as a standard 23 | [cargo example](https://doc.rust-lang.org/cargo/reference/cargo-targets.html#examples) 24 | library, which is compiled into a dynamic library (`liblibrary_tests.dylib`) and loaded by 25 | the Wolfram testing code. The test library is bundled into the built `RustLink` paclet, 26 | along with the other example libraries. 27 | 28 | > The [`cargo-make`](https://crates.io/crates/cargo-make) subcommand can be installed 29 | > using: 30 | > 31 | > ```shell 32 | > cargo install cargo-make 33 | > ``` 34 | 35 | Build the `RustLink` paclet using: 36 | 37 | ```shell 38 | cargo make paclet 39 | ``` 40 | 41 | Run the `wolfram-library-link` tests using: 42 | 43 | ```shell 44 | wolfram-cli paclet test ./build/RustLink 45 | ``` 46 | 47 | This requires that the [unofficial wolfram-cli tool](https://github.com/ConnorGray/wolfram-cli) 48 | is installed, and will run all of the Wolfram `.wlt` test files in the Tests directory, 49 | and output the results to the terminal. 50 | 51 | [cargo-test]: https://doc.rust-lang.org/cargo/commands/cargo-test.html -------------------------------------------------------------------------------- /docs/Maintenance.md: -------------------------------------------------------------------------------- 1 | # Maintenance 2 | 3 | This document describes tasks necessary to maintain the `wolfram-library-link` and 4 | `wolfram-library-link-sys` crates over time. This document is informational and intended 5 | for the maintainer of these crates; users of these crates do not need to read this 6 | document. 7 | 8 | ## Release 9 | 10 | ### Check that common feature combinations compile successfully 11 | 12 | ```shell 13 | $ cargo check --all-features --all-targets --tests --examples --benches 14 | ``` 15 | 16 | ```shell 17 | $ cargo check --no-default-features --all-targets --tests --examples --benches 18 | ``` 19 | 20 | 21 | ## Generating `wolfram-library-link-sys` bindings 22 | 23 | After every Wolfram Language release, the pre-generated bindings stored in the 24 | [`wolfram-library-link-sys/generated/`](../wolfram-library-link-sys/generated/) directory 25 | need to be updated. That directory has the following layout: 26 | 27 | ```text 28 | generated/<$VersionNumber.$ReleaseNumber>/<$SystemID>/LibraryLink_bindings.rs 29 | ``` 30 | 31 | To quickly generate bindings for the current platform, execute: 32 | 33 | ```shell 34 | $ cargo +nightly xtask gen-bindings 35 | ``` 36 | 37 | The new bindings will automatically be placed in the appropriate 38 | sub-directory, and should be commited to this repository. The script will need to be run 39 | on each supported platform. 40 | 41 | #### Example 42 | 43 | From the `RustLink` repository root directory: 44 | 45 | ``` 46 | $ export WOLFRAM_APP_DIRECTORY=/Applications/Wolfram/12.2.x/Mathematica-12.2.0.app 47 | $ cargo +nightly xtask gen-bindings 48 | ``` 49 | 50 | will re-generate the `wolfram-library-link-sys/generated/12.2.0/MacOSX-x86-64/LibraryLink_bindings.rs` 51 | file. 52 | 53 | ## Updating build.rs bindings to use on docs.rs 54 | 55 | When `wolfram-library-link-sys` is built in the environment, some special logic 56 | is required to work around the fact that no Wolfram applications are available to query 57 | for the Wolfram version number. 58 | 59 | At the moment, the [`wolfram-library-link-sys/build.rs`](../wolfram-library-link-sys/build.rs) 60 | file hard-codes a Wolfram version number and System ID to use as the bindings to display 61 | on docs.rs. That version number should be updated each time new `wolfram-library-link-sys` 62 | bindings are generated. -------------------------------------------------------------------------------- /docs/QuickStart.md: -------------------------------------------------------------------------------- 1 | # Quick Start to Wolfram *LibraryLink* for Rust 2 | 3 | Wolfram *LibraryLink* is an interface enabling efficient communication between the 4 | Wolfram Language and loadable dynamic libraries that can provide new high-performance 5 | algorithms, connections to external tools and services, links to other languages, and 6 | more. 7 | 8 | This Quick Start will walk you through how to create a new Rust library that can 9 | be called from the Wolfram Language. This guide will cover: 10 | 11 | * creating a new Rust library crate 12 | - configuring the crate to generate a dynamic library when built 13 | - adding the appropriate `wolfram-library-link` dependency 14 | * writing basic Rust functions that can be called via the *LibraryLink* interface. 15 | * building the library and loading it via [`LibraryFunctionLoad`][ref/LibraryFunctionLoad] 16 | 17 | The library we write will be compiled into a dynamic library, whose functions can be 18 | called directly from the Wolfram Language. 19 | 20 | Instructions for installing and setting up Rust can be found at: 21 | 22 |      23 | 24 | If you do not have the Wolfram Language installed, instructions for downloading the 25 | free Wolfram Engine can be found at: 26 | 27 |      28 | 29 | [ref/LibraryFunctionLoad]: https://reference.wolfram.com/language/ref/LibraryFunctionLoad.html 30 | 31 | ## Create a new library crate 32 | 33 | Assuming that have already installed Rust, you can create a new Rust library from the 34 | command line by running: 35 | 36 | ```shell 37 | $ cargo new --lib my-package 38 | ``` 39 | 40 | This will automatically create a new directory called `my-package`. The `my-package` 41 | directory will contain a `Cargo.toml` file, which contains the specification describing 42 | the new crate. 43 | 44 | > Feel free to choose a name other than `my-package`, though be sure to update the name 45 | > appropriately while following the instructions in this guide. 46 | 47 | #### Configure the crate 48 | 49 | By default, Rust libraries are only built into a format usable as a dependency from other 50 | Rust crates. If we want to build a standalone dynamic library, we'll need to specify that 51 | in the `Cargo.toml` file by setting the 52 | [`crate-type`](https://doc.rust-lang.org/cargo/reference/cargo-targets.html#the-crate-type-field) 53 | property. 54 | 55 | Add the following lines to the `Cargo.toml` file, first creating a new `[lib]` section 56 | following the existing `[package]` section: 57 | 58 | ```toml 59 | [lib] 60 | crate-type = ["cdylib"] 61 | ``` 62 | 63 | Setting `crate-type` to `"cdylib"` instructs the `cargo` build system to produce a dynamic 64 | library that is suitable for loading from C/C++ programs. 65 | 66 | > See the [Linkage](https://doc.rust-lang.org/reference/linkage.html) section of The Rust 67 | > Reference for more information on the different crate type options. 68 | 69 | Next, declare that this crate depends on `wolfram-library-link` by adding the 70 | following line to the `[dependencies]` section: 71 | 72 | ```toml 73 | wolfram-library-link = "X.X.X" 74 | ``` 75 | 76 | A correctly configured Cargo.toml looks like: 77 | 78 | ```toml 79 | ### Cargo.toml 80 | 81 | [package] 82 | # This can be whatever you like, as long as it's a valid crate identifier. 83 | name = "my-package" 84 | version = "0.1.0" 85 | edition = "2021" 86 | 87 | [lib] 88 | crate-type = ["cdylib"] 89 | 90 | [dependencies] 91 | wolfram-library-link = "X.X.X" 92 | ``` 93 | 94 | > See the [Cargo manifest documentation][cargo-manifest-docs] for a complete description 95 | > of the Cargo.toml file. 96 | 97 | ## Write a basic Rust function 98 | 99 | The `cargo new` command used to create the crate will have automatically created a 100 | `my-package/src/lib.rs` file. Replace the existing sample content in that file with 101 | the following Rust code: 102 | 103 | ```rust 104 | use wolfram_library_link as wll; 105 | 106 | #[wll::export] 107 | fn square(x: i64) -> i64 { 108 | x * x 109 | } 110 | ``` 111 | 112 | This is all that is needed to expose a basic Rust function to the Wolfram Language via 113 | the *LibraryLink* interface. The `#[export]` macro automatically generates an efficient 114 | wrapper function that uses the low-level interface expected by *LibraryLink*. 115 | 116 | ## Build and load the library 117 | 118 | Now that we've written a basic library, we can compile it from the command line by 119 | running: 120 | 121 | ```shell 122 | $ cargo build 123 | ``` 124 | 125 | This will automatically fetch any dependencies, and build the dynamic library we specified. 126 | The resulting (unoptimized) library will be located at: 127 | 128 | ```text 129 | my-package/target/debug/libmy_package.dylib 130 | ``` 131 | 132 | > The name of the `my_package` dynamic library file will vary depending on your operating 133 | > system: 134 | > 135 | > * macOS: `libmy_package.dylib` 136 | > * Windows: `my_package.dll` 137 | > * Linux: `libmy_package.so` 138 | 139 | This library can be loaded directly into the Wolfram Language by evaluating: 140 | 141 | ```wolfram 142 | square = LibraryFunctionLoad[ 143 | "/path/to/libmy_package.dylib", 144 | "square", 145 | {Integer}, 146 | Integer 147 | ]; 148 | 149 | square[5] 150 | ``` 151 | 152 | [cargo-manifest-docs]: https://doc.rust-lang.org/cargo/reference/manifest.html -------------------------------------------------------------------------------- /docs/WhyRust.md: -------------------------------------------------------------------------------- 1 | # Why Rust? 2 | 3 | #### *For Wolfram Language Developers* 4 | 5 | The LibraryLink interface and WSTP library offer Wolfram Language developers the 6 | capability to write performant low-level code, interface with external libraries, and 7 | perform system operations, all easily callable from the Wolfram Language. This document 8 | describes some reasons why the Rust programming language might be appealing to Wolfram 9 | Language developers looking to write native code: 10 | 11 | * **Performance** – 12 | Rust is fast. Powerful zero-cost abstractions enable Rust code to easily achieve 13 | performance on par with well-optimized C or C++ programs. 14 | 15 | * **Safety** – 16 | Rust uses zero-cost abstractions to achieve memory-safety[^mem-safe] and 17 | thread-safety[^thread-safe] without garbage collection and unnecessary overhead. Rust 18 | empowers you to drop down into efficient native code without the risk of introducing 19 | crashes or undefined behavior into your Wolfram Language projects. 20 | 21 | Spend less time thinking about pointers and memory, and more time thinking about your 22 | problem domain. 23 | 24 | * **Robust** – 25 | With rich algebraic data types, pattern matching, and simple error propagation idioms, 26 | Rust makes it easy to write code that carefully models complex data and handles error 27 | conditions, without excessive error handling boilerplate. 28 | 29 | #### High-level features 30 | 31 | 33 | 34 | * **Expression-oriented** — 35 | Function bodies[^func-ret-expr], and statements like [`if`][if], [`match`][match], and 36 | [`loop`][loop] all yield a value. No need for ternary operators and procedural `return` 37 | statements. 38 | 39 | * **Functional** — 40 | Rust functions can be used as values, passed as arguments, and stored. Closures[^closures] 41 | can capture variables from their environment and be abstracted over generically. 42 | Iterator combinators can be used to filter, map, fold, etc. sequencial 43 | data.[^iter] 44 | 45 | * **Immutable by default** – 46 | In Rust, all variables and references are immutable by default. The [`mut`][mut] keyword 47 | is used to make the possibility of mutation explicit. 48 | 49 | * **Algebraic data types** — 50 | Structs, tuples, and [`enum`][enum]s with associated data give the programmer 51 | flexibility and precision when modeling their problem domain. Use pattern 52 | matching[^pattern-matching] to access values quickly and robustly. 53 | 54 | #### High-leverage tools 55 | 56 | * **Dependency management** – 57 | Rust has built-in support for managing dependencies, via the [`cargo`][cargo] command-line 58 | tool. Use `cargo` to automatically download your dependencies, build your library, run 59 | tests, and more. Easily use any of the tens of thousands of existing libraries from 60 | [crates.io](https://crates.io) in your project. 61 | 62 | * E.g. the [`rayon`](https://crates.io/crates/rayon) crate makes it trivial to add 63 | multithreading to code that processes elements sequencially. 64 | 65 | * **Testing** – 66 | Use [`cargo test`][cargo-test] to run all unit, integration, and doc tests[^doc-tests] 67 | in your package. Define [unit tests][unit-tests] alongside your library code to ensure 68 | they stay up to date. 69 | 70 | * **Benchmarking** – 71 | Use [`cargo bench`][cargo-bench] to run benchmarks defined by your package. 72 | 73 | ### Why `wolfram-library-link`? 74 | 75 | TODO 76 | 77 | #### Interesting in trying Rust? 78 | 79 | The [**Quick Start**](./QuickStart.md) guide contains instructions on how to write a 80 | basic Wolfram LibraryLink program in Rust. 81 | 82 | 83 | 84 | 85 | 86 | [^mem-safe]: 87 | The [Understanding Ownership][UnderstandingOwnership] chapter of the Rust Book describes 88 | how the concepts of *ownership* and *borrowing* are central to Rust's memory safety 89 | guarantees. This [StackOverflow answer](https://stackoverflow.com/a/36137381) contains 90 | a good informal description of ownership and borrowing. 91 | 92 | [^thread-safe]: 93 | Rust's [`Send`][Send] and [`Sync`][Sync] traits model the properties of data that can 94 | safely be shared and accessed concurrently in multi-threaded programs. The 95 | [Fearless Concurrency][FearlessConcurrency] post from the Rust Blog is an accessible 96 | introduction to how Rust's concepts of *ownership* and *borrowing* lead naturally to 97 | how thread-safety is modeled. 98 | 101 | 102 | [^func-ret-expr]: If the last statement in a function is an expression, that value is 103 | [returned from the function](https://doc.rust-lang.org/book/ch03-03-how-functions-work.html#functions-with-return-values) 104 | 105 | [^closures]: See the Rust By Example chapter on 106 | [Closures](https://doc.rust-lang.org/rust-by-example/fn/closures.html). 107 | 108 | [^iter]: See the Rust by Example chapter on 109 | [Iterators](https://doc.rust-lang.org/rust-by-example/trait/iter.html) 110 | 111 | [^pattern-matching]: Rust has first-class support for pattern matching: [`let`][let], 112 | [`match`][match], [`if let`][if-let], [`while let`][while-let], function parameters, 113 | and more all support pattern matching over struct, tuple, array, and enum values. See 114 | the [Patterns and Matching][PatternsAndMatching] chapter of the Rust Book. 115 | 116 | [^doc-tests]: In addition to supporting standard unit- and integration-style tests, Rust 117 | also supports running code samples that appear in documentation comments as test cases. 118 | Called [documentation tests](https://doc.rust-lang.org/rustdoc/documentation-tests.html), 119 | this capability ensures that Rust code samples appearing in your library documentation 120 | are valid and up to date. 121 | 122 | 123 | 124 | 125 | 126 | [if]: https://doc.rust-lang.org/std/keyword.if.html 127 | [loop]: https://doc.rust-lang.org/std/keyword.loop.html 128 | [match]: https://doc.rust-lang.org/std/keyword.match.html 129 | [enum]: https://doc.rust-lang.org/std/keyword.enum.html 130 | [mut]: https://doc.rust-lang.org/std/keyword.mut.html 131 | [let]: https://doc.rust-lang.org/std/keyword.let.html 132 | 133 | [if-let]: https://doc.rust-lang.org/rust-by-example/flow_control/if_let.html 134 | [while-let]: https://doc.rust-lang.org/rust-by-example/flow_control/while_let.html 135 | 136 | [unit-tests]: https://doc.rust-lang.org/book/ch11-01-writing-tests.html#the-anatomy-of-a-test-function 137 | 138 | [Send]: https://doc.rust-lang.org/std/marker/trait.Send.html 139 | [Sync]: https://doc.rust-lang.org/std/marker/trait.Sync.html 140 | 141 | [FearlessConcurrency]: https://blog.rust-lang.org/2015/04/10/Fearless-Concurrency.html 142 | [UnderstandingOwnership]: https://doc.rust-lang.org/book/ch04-00-understanding-ownership.html 143 | [PatternsAndMatching]: https://doc.rust-lang.org/book/ch18-00-patterns.html 144 | [Rustonomicon/Send-and-Sync]: https://doc.rust-lang.org/nomicon/send-and-sync.html 145 | 146 | [cargo]: https://doc.rust-lang.org/cargo/getting-started/first-steps.html 147 | [cargo-test]: https://doc.rust-lang.org/cargo/commands/cargo-test.html 148 | [cargo-bench]: https://doc.rust-lang.org/cargo/commands/cargo-bench.html -------------------------------------------------------------------------------- /scripts/BuildPaclet.wls: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env wolframscript 2 | (* ::Package:: *) 3 | 4 | (* ::Title:: *) 5 | (*Build Paclet*) 6 | 7 | 8 | Needs["PacletTools`"] 9 | Needs["CodeFormatter`"] 10 | 11 | (*----------------------------------------------------------*) 12 | (* Verify that Directory[] is the repository root directory *) 13 | (*----------------------------------------------------------*) 14 | 15 | $repositoryDir = Directory[]; 16 | 17 | If[FileNameTake[$repositoryDir] =!= "wolfram-library-link-rs", 18 | Throw[StringForm["Unexpected repository root directory path: ``", $repositoryDir]] 19 | ]; 20 | 21 | (*-------------------------------------------------------------*) 22 | (* Build the RustLink paclet into $repositoryDir/build/ *) 23 | (*-------------------------------------------------------------*) 24 | 25 | (* ::Subsubsection:: *) 26 | (*Build the paclet*) 27 | 28 | 29 | result = PacletBuild[ 30 | FileNameJoin[{$repositoryDir, "wolfram-library-link", "RustLink"}], 31 | FileNameJoin[{$repositoryDir, "build"}] 32 | ]; 33 | 34 | If[FailureQ[result], 35 | Echo @ CodeFormat @ ToString[result, InputForm]; 36 | Exit[-1]; 37 | ]; 38 | 39 | (*-----------------------------------------------------------------------------------*) 40 | (* Copy the examples and tests dynamic libraries into the LibraryResources directory *) 41 | (*-----------------------------------------------------------------------------------*) 42 | 43 | (* ::Subsubsection:: *) 44 | (*Copy LibraryResources libraries*) 45 | 46 | 47 | $dir = FileNameJoin[{$repositoryDir, "build", "RustLink", "LibraryResources", $SystemID}]; 48 | 49 | 50 | CreateDirectory[$dir] 51 | 52 | 53 | Scan[ 54 | exampleLib |-> CopyFile[ 55 | FileNameJoin[{"target", "debug", "examples", exampleLib}], 56 | FileNameJoin[{$dir, exampleLib}] 57 | ], 58 | { 59 | (* Library Tests *) 60 | "liblibrary_tests.dylib", 61 | (* Examples *) 62 | "libbasic_types.dylib", 63 | "libraw_wstp_function.dylib", 64 | "libbasic_expressions.dylib", 65 | "libmanaged_exprs.dylib", 66 | "libnumeric_arrays.dylib", 67 | "libraw_librarylink_function.dylib", 68 | "libasync_file_watcher_raw.dylib", 69 | "libasync_file_watcher.dylib", 70 | "libdata_store.dylib", 71 | "libaborts.dylib", 72 | "libwstp_example.dylib", 73 | "libwll_docs.dylib" 74 | } 75 | ] 76 | -------------------------------------------------------------------------------- /scripts/re_build_WLLibraryLink.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /wolfram-library-link-sys/.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | **/*.rs.bk 3 | Cargo.lock 4 | -------------------------------------------------------------------------------- /wolfram-library-link-sys/.rustfmt.toml: -------------------------------------------------------------------------------- 1 | max_width = 90 2 | comment_width = 90 3 | match_block_trailing_comma = true 4 | blank_lines_upper_bound = 2 5 | merge_derives = false 6 | overflow_delimited_expr = true 7 | -------------------------------------------------------------------------------- /wolfram-library-link-sys/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "wolfram-library-link-sys" 3 | version = "0.2.10" 4 | authors = ["Connor Gray "] 5 | license = "MIT OR Apache-2.0" 6 | edition = "2018" 7 | readme = "README.md" 8 | repository = "https://github.com/WolframResearch/wolfram-library-link-rs" 9 | description = "Low-level FFI bindings to the Wolfram LibraryLink C API" 10 | keywords = ["wolfram-library-link", "wstp", "wolfram", "wolfram-language", "wolfram-engine"] 11 | categories = ["external-ffi-bindings", "development-tools::ffi"] 12 | 13 | # TODO: Use the 'links' key? 14 | # See: https://doc.rust-lang.org/cargo/reference/build-scripts.html#a-sys-packages 15 | 16 | [dependencies] 17 | 18 | [build-dependencies] 19 | env_logger = "0.10.0" 20 | 21 | wolfram-app-discovery = "0.4.7" 22 | -------------------------------------------------------------------------------- /wolfram-library-link-sys/README.md: -------------------------------------------------------------------------------- 1 | # wolfram-library-link-sys 2 | 3 | Low-level FFI bindings to the 4 | [Wolfram LibraryLink C API](https://reference.wolfram.com/language/LibraryLink/tutorial/LibraryStructure.html). 5 | 6 | The [`wolfram-library-link`](https://crates.io/crates/wolfram-library-link) crate provides 7 | efficient and idiomatic Rust bindings to Wolfram LibraryLink based on these raw bindings. -------------------------------------------------------------------------------- /wolfram-library-link-sys/build.rs: -------------------------------------------------------------------------------- 1 | use std::path::PathBuf; 2 | 3 | use wolfram_app_discovery::{SystemID, WolframVersion}; 4 | 5 | /// Oldest Wolfram Version that wolfram-library-link is compatible with. 6 | const WOLFRAM_VERSION: WolframVersion = WolframVersion::new(13, 0, 1); 7 | 8 | fn main() { 9 | env_logger::init(); 10 | 11 | // Ensure that changes to environment variables checked by wolfram-app-discovery will 12 | // cause cargo to rebuild the current crate. 13 | wolfram_app_discovery::config::set_print_cargo_build_script_directives(true); 14 | 15 | // This crate is being built by docs.rs. Skip trying to locate a WolframApp. 16 | // See: https://docs.rs/about/builds#detecting-docsrs 17 | if std::env::var("DOCS_RS").is_ok() { 18 | // Force docs.rs to use the bindings generated for this version / system. 19 | let bindings_path = make_bindings_path(WOLFRAM_VERSION, SystemID::MacOSX_x86_64); 20 | 21 | // This environment variable is included using `env!()`. wolfram-library-link-sys 22 | // will fail to build if it is not set correctly. 23 | println!( 24 | "cargo:rustc-env=CRATE_WOLFRAM_LIBRARYLINK_SYS_BINDINGS={}", 25 | bindings_path.display() 26 | ); 27 | 28 | return; 29 | } 30 | 31 | //----------------------------------------------------------- 32 | // Generate or use pre-generated Rust bindings to LibraryLink 33 | //----------------------------------------------------------- 34 | // See docs/Maintenance.md for instructions on how to generate 35 | // bindings for new WL versions. 36 | 37 | let bindings_path = use_pregenerated_bindings(); 38 | 39 | println!( 40 | "cargo:rustc-env=CRATE_WOLFRAM_LIBRARYLINK_SYS_BINDINGS={}", 41 | bindings_path.display() 42 | ); 43 | } 44 | 45 | //======================================================================== 46 | // Tell `lib.rs` where to find the file containing the WSTP Rust bindings. 47 | //======================================================================== 48 | 49 | //----------------------- 50 | // Pre-generated bindings 51 | //----------------------- 52 | 53 | fn use_pregenerated_bindings() -> PathBuf { 54 | let system_id = SystemID::try_from_rust_target(&std::env::var("TARGET").unwrap()) 55 | .expect("unable to get System ID for target system"); 56 | 57 | // FIXME: Check that this file actually exists, and generate a nicer error if it 58 | // doesn't. 59 | 60 | let bindings_path = make_bindings_path(WOLFRAM_VERSION, system_id); 61 | 62 | println!("cargo:rerun-if-changed={}", bindings_path.display()); 63 | 64 | if !bindings_path.is_file() { 65 | println!( 66 | " 67 | ==== ERROR: wolfram-library-link-sys ===== 68 | 69 | Rust bindings for Wolfram LibraryLink for target configuration: 70 | 71 | WolframVersion: {} 72 | SystemID: {} 73 | 74 | have not been pre-generated. 75 | 76 | See wolfram-library-link-sys/generated/ for a listing of currently available targets. 77 | 78 | ========================================= 79 | ", 80 | WOLFRAM_VERSION, system_id 81 | ); 82 | panic!(""); 83 | } 84 | 85 | bindings_path 86 | } 87 | 88 | /// Path (relative to the crate root directory) to the bindings file. 89 | fn make_bindings_path(wolfram_version: WolframVersion, system_id: SystemID) -> PathBuf { 90 | // Path (relative to the crate root directory) to the bindings file. 91 | let bindings_path = PathBuf::from("generated") 92 | .join(&wolfram_version.to_string()) 93 | .join(system_id.as_str()) 94 | .join("LibraryLink_bindings.rs"); 95 | 96 | println!( 97 | "cargo:warning=info: using LibraryLink bindings from: {}", 98 | bindings_path.display() 99 | ); 100 | 101 | let absolute_bindings_path = 102 | PathBuf::from(std::env::var("CARGO_MANIFEST_DIR").unwrap()).join(&bindings_path); 103 | 104 | absolute_bindings_path 105 | } 106 | -------------------------------------------------------------------------------- /wolfram-library-link-sys/src/lib.rs: -------------------------------------------------------------------------------- 1 | //! Automatically generated bindings to the Wolfram LibraryLink C API. 2 | 3 | #![allow(non_snake_case, non_upper_case_globals, non_camel_case_types)] 4 | #![allow(deref_nullptr)] 5 | 6 | // The name of this file comes from `build.rs`. 7 | include!(env!("CRATE_WOLFRAM_LIBRARYLINK_SYS_BINDINGS")); 8 | -------------------------------------------------------------------------------- /wolfram-library-link/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "wolfram-library-link" 3 | version = "0.2.10" 4 | authors = ["Connor Gray "] 5 | license = "MIT OR Apache-2.0" 6 | edition = "2021" 7 | readme = "../README.md" 8 | repository = "https://github.com/WolframResearch/wolfram-library-link-rs" 9 | description = "Bindings to Wolfram LibraryLink" 10 | keywords = ["wolfram-library-link", "wstp", "wolfram", "wolfram-language", "wolfram-engine"] 11 | categories = ["external-ffi-bindings", "development-tools::ffi"] 12 | 13 | include = [ 14 | "/src", 15 | "/examples", 16 | # Files in this directory are included in the `wolfram_library_link::docs` module 17 | # via `#[doc = include_str!()]`, so these need to be included in the published crate 18 | # in order for the docs.rs build to succeed. 19 | "/RustLink/Examples/Docs" 20 | ] 21 | 22 | [dependencies] 23 | wolfram-library-link-macros = { version = "0.2.10", path = "./wolfram-library-link-macros" } 24 | 25 | wolfram-library-link-sys = { version = "0.2.10", path = "../wolfram-library-link-sys" } 26 | 27 | wstp = "0.2.8" 28 | wolfram-expr = "0.1.0" 29 | 30 | once_cell = "1.8.0" 31 | static_assertions = "1.1.0" 32 | ref-cast = "1.0.6" 33 | 34 | backtrace = { version = "^0.3.46", optional = true } 35 | inventory = { version = "0.2.1", optional = true } 36 | process_path = { version = "0.1.3", optional = true } 37 | 38 | [dev-dependencies] 39 | 40 | [features] 41 | default = ["panic-failure-backtraces", "automate-function-loading-boilerplate"] 42 | nightly = [] 43 | 44 | panic-failure-backtraces = ["backtrace"] 45 | automate-function-loading-boilerplate = ["inventory", "process_path", "wolfram-library-link-macros/automate-function-loading-boilerplate"] 46 | 47 | 48 | #======================================= 49 | # Examples 50 | #======================================= 51 | 52 | [[example]] 53 | name = "basic_types" 54 | crate-type = ["cdylib"] 55 | required-features = ["automate-function-loading-boilerplate"] 56 | 57 | [[example]] 58 | name = "numeric_arrays" 59 | crate-type = ["cdylib"] 60 | 61 | [[example]] 62 | name = "data_store" 63 | crate-type = ["cdylib"] 64 | 65 | [[example]] 66 | name = "aborts" 67 | crate-type = ["cdylib"] 68 | 69 | [[example]] 70 | name = "wstp_example" # avoid "libwstp.dylib", which seems too generic. 71 | path = "examples/wstp.rs" 72 | crate-type = ["cdylib"] 73 | required-features = ["automate-function-loading-boilerplate"] 74 | 75 | #----------------------------- 76 | # Raw (unsafe, low-level) APIs 77 | #----------------------------- 78 | 79 | [[example]] 80 | name = "raw_wstp_function" 81 | path = "examples/raw/raw_wstp_function.rs" 82 | crate-type = ["cdylib"] 83 | 84 | [[example]] 85 | name = "raw_librarylink_function" 86 | path = "examples/raw/raw_librarylink_function.rs" 87 | crate-type = ["cdylib"] 88 | 89 | #------------ 90 | # Expressions 91 | #------------ 92 | 93 | [[example]] 94 | name = "basic_expressions" 95 | path = "examples/exprs/basic_expressions.rs" 96 | crate-type = ["cdylib"] 97 | 98 | [[example]] 99 | name = "managed_exprs" 100 | path = "examples/exprs/managed.rs" 101 | crate-type = ["cdylib"] 102 | required-features = ["automate-function-loading-boilerplate"] 103 | 104 | #--------------- 105 | # Async examples 106 | #--------------- 107 | 108 | [[example]] 109 | name = "async_file_watcher" 110 | path = "examples/async/async_file_watcher.rs" 111 | crate-type = ["cdylib"] 112 | 113 | [[example]] 114 | name = "async_file_watcher_raw" 115 | path = "examples/async/async_file_watcher_raw.rs" 116 | crate-type = ["cdylib"] 117 | 118 | #--------------- 119 | # Examples from the wolfram_library_link::docs module 120 | #--------------- 121 | 122 | [[example]] 123 | name = "wll_docs" 124 | path = "examples/docs/main.rs" 125 | crate-type = ["cdylib"] 126 | 127 | #--------------- 128 | # Tests -- see example/tests/README.md 129 | #--------------- 130 | 131 | [[example]] 132 | name = "library_tests" 133 | path = "examples/tests/main.rs" 134 | crate-type = ["cdylib"] -------------------------------------------------------------------------------- /wolfram-library-link/RustLink/Examples/Aborts.wlt: -------------------------------------------------------------------------------- 1 | Needs["MUnit`"] 2 | 3 | Test[ 4 | (* Use TimeConstrained to start an abort that will trigger after we've started 5 | executing the Rust library function. *) 6 | TimeConstrained[ 7 | LibraryFunctionLoad[ 8 | "libaborts", 9 | "wait_for_abort", 10 | {}, 11 | Integer 12 | ][] 13 | , 14 | (* Wait for a tenth of a second. *) 15 | 0.25 16 | ] 17 | , 18 | $Aborted 19 | ] -------------------------------------------------------------------------------- /wolfram-library-link/RustLink/Examples/AsyncExamples.wlt: -------------------------------------------------------------------------------- 1 | Needs["MUnit`"] 2 | 3 | (* Test the async_file_watcher_raw.rs example. *) 4 | Test[ 5 | delay = 100; 6 | file = CreateFile[]; 7 | 8 | $changes = {}; 9 | 10 | eventHandler[ 11 | taskObject_, 12 | "change", 13 | {modTime_} 14 | ] := AppendTo[$changes, modTime]; 15 | 16 | (* Begin the background task. *) 17 | task = Internal`CreateAsynchronousTask[ 18 | LibraryFunctionLoad[ 19 | "libasync_file_watcher_raw", 20 | "start_file_watcher", 21 | {Integer, "UTF8String"}, 22 | Integer 23 | ], 24 | {delay, file}, 25 | eventHandler 26 | ]; 27 | 28 | (* Modify the watched file. *) 29 | Put[1, file]; 30 | expectedModifiedTime = UnixTime[]; 31 | 32 | (* Ensure the file modification check has time to run. *) 33 | Pause[Quantity[2 * delay, "Milliseconds"]]; 34 | 35 | StopAsynchronousTask[task]; 36 | 37 | $changes 38 | , 39 | {expectedModifiedTime} 40 | ] 41 | 42 | (* Test the async_file_watcher.rs example. This is identical to the above test, except the 43 | example implementation uses the safe wrappers. *) 44 | Test[ 45 | delay = 100; 46 | file = CreateFile[]; 47 | 48 | $changes2 = {}; 49 | 50 | eventHandler[ 51 | taskObject_, 52 | "change", 53 | {modTime_} 54 | ] := AppendTo[$changes2, modTime]; 55 | 56 | (* Begin the background task. *) 57 | task = Internal`CreateAsynchronousTask[ 58 | LibraryFunctionLoad[ 59 | "libasync_file_watcher", 60 | "start_file_watcher", 61 | {Integer, "UTF8String"}, 62 | Integer 63 | ], 64 | {delay, file}, 65 | eventHandler 66 | ]; 67 | 68 | (* Modify the watched file. *) 69 | Put[1, file]; 70 | expectedModifiedTime = UnixTime[]; 71 | 72 | (* Ensure the file modification check has time to run. *) 73 | Pause[Quantity[2 * delay, "Milliseconds"]]; 74 | 75 | StopAsynchronousTask[task]; 76 | 77 | $changes2 78 | , 79 | {expectedModifiedTime} 80 | ] -------------------------------------------------------------------------------- /wolfram-library-link/RustLink/Examples/BasicTypes.wlt: -------------------------------------------------------------------------------- 1 | Needs["MUnit`"] 2 | 3 | (* Test the loader function generated by `generate_loader!`. *) 4 | TestMatch[ 5 | load = LibraryFunctionLoad[ 6 | "libbasic_types", 7 | "load_basic_types_functions", 8 | LinkObject, 9 | LinkObject 10 | ]; 11 | 12 | $functions = load["libbasic_types"]; 13 | 14 | Sort[$functions] 15 | , 16 | <| 17 | "add2" -> LibraryFunction[_, "add2", {Integer, Integer}, Integer], 18 | "add3" -> LibraryFunction[_, "add3", {Integer, Integer, Integer}, Integer], 19 | "positive_i64" -> LibraryFunction[ 20 | _, 21 | "positive_i64", 22 | {{LibraryDataType[NumericArray, "Integer64"], "Constant"}}, 23 | LibraryDataType[NumericArray, "UnsignedInteger8"] 24 | ], 25 | "reverse_string" -> LibraryFunction[ 26 | _, 27 | "reverse_string", 28 | {"UTF8String"}, 29 | "UTF8String" 30 | ], 31 | "square" -> LibraryFunction[_, "square", {Integer}, Integer], 32 | "total_i64" -> LibraryFunction[ 33 | _, 34 | "total_i64", 35 | {{LibraryDataType[NumericArray, "Integer64"], "Constant"}}, 36 | Integer 37 | ], 38 | "xkcd_get_random_number" -> LibraryFunction[_, "xkcd_get_random_number", {}, Integer] 39 | |> 40 | ] 41 | 42 | Test[ 43 | square = $functions["square"]; 44 | 45 | square[11] 46 | , 47 | 121 48 | ] 49 | 50 | Test[ 51 | reverseString = $functions["reverse_string"]; 52 | 53 | reverseString["hello world"] 54 | , 55 | "dlrow olleh" 56 | ] 57 | 58 | Test[ 59 | add2 = $functions["add2"]; 60 | 61 | add2[3, 3] 62 | , 63 | 6 64 | ] 65 | 66 | Test[ 67 | totalI64 = $functions["total_i64"]; 68 | 69 | totalI64[NumericArray[Range[100], "Integer64"]] 70 | , 71 | 5050 72 | ] 73 | 74 | Test[ 75 | positiveQ = $functions["positive_i64"]; 76 | 77 | positiveQ[NumericArray[{0, 1, -2, 3, 4, -5}, "Integer64"]] 78 | , 79 | NumericArray[{0, 1, 0, 1, 1, 0}, "UnsignedInteger8"] 80 | ] 81 | 82 | Test[ 83 | randomNumber = $functions["xkcd_get_random_number"]; 84 | 85 | randomNumber[] 86 | , 87 | 4 88 | ] 89 | 90 | Test[ 91 | rawSquare = LibraryFunctionLoad[ 92 | "libbasic_types", 93 | "raw_square", 94 | {Integer}, 95 | Integer 96 | ]; 97 | 98 | rawSquare[50] 99 | , 100 | 2500 101 | ] -------------------------------------------------------------------------------- /wolfram-library-link/RustLink/Examples/DataStore.wlt: -------------------------------------------------------------------------------- 1 | Needs["MUnit`"] 2 | 3 | Test[ 4 | LibraryFunctionLoad[ 5 | "libdata_store", 6 | "string_join", 7 | {"DataStore"}, 8 | String 9 | ][ 10 | Developer`DataStore["hello", " ", "world"] 11 | ] 12 | , 13 | "hello world" 14 | ] -------------------------------------------------------------------------------- /wolfram-library-link/RustLink/Examples/Docs/Convert/ManualWSTP.wlt: -------------------------------------------------------------------------------- 1 | VerificationTest[ 2 | createPoint = LibraryFunctionLoad["libwll_docs", "create_point", LinkObject, LinkObject]; 3 | 4 | createPoint[] 5 | , 6 | Point[{1.0, 2.0}] 7 | ] -------------------------------------------------------------------------------- /wolfram-library-link/RustLink/Examples/Docs/Convert/UsingExpr.wlt: -------------------------------------------------------------------------------- 1 | VerificationTest[ 2 | createPoint = LibraryFunctionLoad["libwll_docs", "create_point2", LinkObject, LinkObject]; 3 | 4 | createPoint[] 5 | , 6 | Point[{3.0, 4.0}] 7 | ] -------------------------------------------------------------------------------- /wolfram-library-link/RustLink/Examples/Docs/EvaluateWolframCodeFromRust/GenerateMessage.wlt: -------------------------------------------------------------------------------- 1 | MySymbol::msg = "This is a message generated from ``"; 2 | 3 | (* FIXME: For some reason, the test below fails with the following message unless 4 | we _save the result_ of calling Links[]: 5 | LinkObject::linkd: Unable to communicate with closed link LinkObject[...] 6 | Note that this only happens when running the tests using 7 | `wolfram-cli paclet test`, so it's likely this is some unknown conflict. 8 | *) 9 | before = Links[]; 10 | 11 | VerificationTest[ 12 | generateMessage = LibraryFunctionLoad[ 13 | "libwll_docs", "generate_message", 14 | LinkObject, LinkObject 15 | ]; 16 | 17 | (* Note: 18 | Set $Context and $ContextPath to force symbols sent 19 | via WSTP to include their context. *) 20 | Block[{$Context = "Empty`", $ContextPath = {}}, 21 | generateMessage[] 22 | ] 23 | , 24 | Null 25 | , 26 | {HoldForm[Message[MySymbol::msg, "a Rust LibraryLink function"]]} 27 | ] -------------------------------------------------------------------------------- /wolfram-library-link/RustLink/Examples/Expressions.wlt: -------------------------------------------------------------------------------- 1 | Needs["MUnit`"] 2 | 3 | TestMatch[ 4 | func = LibraryFunctionLoad[ 5 | "libbasic_expressions", 6 | "echo_arguments", 7 | LinkObject, 8 | LinkObject 9 | ]; 10 | 11 | func[2, 2] 12 | , 13 | (* "finished echoing 2 argument(s)" *) 14 | (* FIXME: This output is a bug. Fix the bug and update this test case. *) 15 | Failure["RustPanic", <| 16 | "MessageTemplate" -> "Rust LibraryLink function panic: `message`", 17 | "MessageParameters" -> <| 18 | "message" -> "evaluate(): evaluation of expression failed: WSTP error: symbol name 'ReturnPacket' has no context: \n\texpression: System`Echo[2]" 19 | |>, 20 | "SourceLocation" -> s_?StringQ /; StringStartsQ[s, "wolfram-library-link/src/lib.rs:"], 21 | "Backtrace" -> Missing["NotEnabled"] 22 | |>] 23 | ] 24 | -------------------------------------------------------------------------------- /wolfram-library-link/RustLink/Examples/ManagedExpressions.wlt: -------------------------------------------------------------------------------- 1 | Needs["MUnit`"] 2 | 3 | TestMatch[ 4 | loadFunctions = LibraryFunctionLoad[ 5 | "libmanaged_exprs", 6 | "load_managed_exprs_functions", 7 | LinkObject, 8 | LinkObject 9 | ]; 10 | 11 | $functions = loadFunctions["libmanaged_exprs"] // Sort 12 | , 13 | <| 14 | "get_instance_data" -> Function[___], 15 | "set_instance_value" -> Function[___] 16 | |> 17 | ] 18 | 19 | Test[ 20 | $obj = CreateManagedLibraryExpression["my_object", MyObject]; 21 | 22 | MatchQ[$obj, MyObject[1]] 23 | ] 24 | 25 | Test[ 26 | ManagedLibraryExpressionQ[$obj] 27 | ] 28 | 29 | Test[ 30 | $objID = ManagedLibraryExpressionID[$obj]; 31 | 32 | MatchQ[$objID, 1] 33 | ] 34 | 35 | Test[ 36 | $functions["get_instance_data"][$objID] 37 | , 38 | <| "Value" -> "default" |> 39 | ] 40 | 41 | Test[ 42 | $functions["set_instance_value"][$objID, "new value"] 43 | , 44 | Null 45 | ] 46 | 47 | Test[ 48 | $functions["get_instance_data"][$objID] 49 | , 50 | <| "Value" -> "new value" |> 51 | ] 52 | 53 | TestMatch[ 54 | (* Clear $obj. This is the last copy of this managed expression, so the Kernel will 55 | call managed.rs/manage_instance() with a `ManagedExpressionEvent::Drop(_)` event. 56 | 57 | The fact that `ClearAll[..]` (or $obj going "out of scope" naturally) has the 58 | effect of calling back into the library to deallocate the object instance is the 59 | key feature of managed library expressions. 60 | *) 61 | ClearAll[$obj]; 62 | 63 | $functions["get_instance_data"][$objID] 64 | , 65 | (* Test that trying to access a deallocated instance fails. *) 66 | Failure["RustPanic", <| 67 | "MessageTemplate" -> "Rust LibraryLink function panic: `message`", 68 | "MessageParameters" -> <|"message" -> "instance does not exist"|>, 69 | "SourceLocation" -> s_?StringQ /; StringStartsQ[s, "wolfram-library-link/examples/exprs/managed.rs:"], 70 | "Backtrace" -> Missing["NotEnabled"] 71 | |>] 72 | ] -------------------------------------------------------------------------------- /wolfram-library-link/RustLink/Examples/NumericArrays.wlt: -------------------------------------------------------------------------------- 1 | Needs["MUnit`"] 2 | 3 | Test[ 4 | func = LibraryFunctionLoad[ 5 | "libnumeric_arrays", 6 | "sum_int_numeric_array", 7 | {NumericArray}, 8 | Integer 9 | ]; 10 | 11 | { 12 | func[NumericArray[Range[10], "Integer64"]], 13 | func[NumericArray[Range[255], "UnsignedInteger8"]] 14 | } 15 | , 16 | { 17 | 55, 18 | 32640 19 | } 20 | ] 21 | 22 | Test[ 23 | func = LibraryFunctionLoad[ 24 | "libnumeric_arrays", 25 | "sum_real_numeric_array", 26 | {NumericArray}, 27 | Real 28 | ]; 29 | 30 | { 31 | func[NumericArray[Range[1, 10, 1/81], "Real32"]], 32 | func[NumericArray[Range[1, 10, 1/81], "Real64"]] 33 | } 34 | , 35 | { 36 | 4015.0, 37 | 4015.0 38 | } 39 | ] 40 | -------------------------------------------------------------------------------- /wolfram-library-link/RustLink/Examples/RawFunctions.wlt: -------------------------------------------------------------------------------- 1 | Needs["MUnit`"] 2 | 3 | (*======================================*) 4 | (* Raw WSTP Functions *) 5 | (*======================================*) 6 | 7 | Test[ 8 | func = LibraryFunctionLoad[ 9 | "libraw_wstp_function", 10 | "demo_wstp_function", 11 | LinkObject, 12 | LinkObject 13 | ]; 14 | 15 | func[2, 2] 16 | , 17 | 4 18 | ] 19 | 20 | Test[ 21 | func = LibraryFunctionLoad[ 22 | "libraw_wstp_function", 23 | "demo_wstp_function_callback", 24 | LinkObject, 25 | LinkObject 26 | ]; 27 | 28 | func[] 29 | , 30 | "returned normally" 31 | ] 32 | 33 | Test[ 34 | func = LibraryFunctionLoad[ 35 | "libraw_wstp_function", 36 | "wstp_expr_function", 37 | LinkObject, 38 | LinkObject 39 | ]; 40 | 41 | func[{1, 2, 3}] 42 | , 43 | (* FIXME: This output is a bug. Fix the bug and update this test case. *) 44 | Failure["WSTP Error", <| 45 | "Message" -> "WSTP error: symbol name 'List' has no context" 46 | |>] 47 | ] -------------------------------------------------------------------------------- /wolfram-library-link/RustLink/Examples/WSTP.wlt: -------------------------------------------------------------------------------- 1 | Needs["MUnit`"] 2 | 3 | (* Test the loader function generated by `generate_loader!`. *) 4 | TestMatch[ 5 | load = LibraryFunctionLoad[ 6 | "libwstp_example", 7 | "load_wstp_functions", 8 | LinkObject, 9 | LinkObject 10 | ]; 11 | 12 | $functions = load["libwstp_example"]; 13 | 14 | Sort[$functions] 15 | , 16 | (* 17 | These functions will typically have a structure similar to the following: 18 | 19 | Function[ 20 | Block[{$Context = "RustLinkWSTPPrivateContext`", $ContextPath = {}}, 21 | LibraryFunction["/path/to/libwstp_example.dylib", "count_args", LinkObject][ 22 | ## 23 | ] 24 | ] 25 | ] 26 | 27 | Setting $Context and $ContextPath before each call to the WSTP library 28 | function is necessary to force all symbols sent across the link to be 29 | formatted with their context name included. See the 'Symbol contexts problem' 30 | section in the crate documentation for more information. 31 | *) 32 | <| 33 | "count_args" -> Function[__], 34 | "expr_string_join" -> Function[__], 35 | "link_expr_identity" -> Function[__], 36 | "square_wstp" -> Function[__], 37 | "string_join" -> Function[__], 38 | "total" -> Function[__], 39 | "total_args_i64" -> Function[__] 40 | |> 41 | ] 42 | 43 | Test[ 44 | $functions["square_wstp"][4] 45 | , 46 | 16 47 | ] 48 | 49 | (* Test that passing more than one argument to square_wstp() results in a Failure. *) 50 | TestMatch[ 51 | $functions["square_wstp"][4, 4] 52 | , 53 | Failure["RustPanic", <| 54 | "MessageTemplate" -> "Rust LibraryLink function panic: `message`", 55 | "MessageParameters" -> <|"message" -> "square_wstp: expected to get a single argument"|>, 56 | "SourceLocation" -> s_?StringQ /; StringStartsQ[s, "wolfram-library-link/examples/wstp.rs:"], 57 | "Backtrace" -> Missing["NotEnabled"] 58 | |>] 59 | ] 60 | 61 | Test[ 62 | $functions["count_args"][a, b, c] 63 | , 64 | 3 65 | ] 66 | 67 | Test[ 68 | totalArgsI64 = $functions["total_args_i64"]; 69 | 70 | { 71 | totalArgsI64[2, 2], 72 | totalArgsI64[1, 2, 3] 73 | } 74 | , 75 | { 76 | 4, 77 | 6 78 | } 79 | ] 80 | 81 | Test[ 82 | stringJoin = $functions["string_join"]; 83 | 84 | { 85 | stringJoin["Hello, ", "World!"], 86 | stringJoin[Sequence @@ CharacterRange["A", "G"]], 87 | stringJoin[] 88 | }, 89 | { 90 | "Hello, World!", 91 | "ABCDEFG", 92 | "" 93 | } 94 | ] 95 | 96 | TestMatch[ 97 | linkExprIdentity = $functions["link_expr_identity"]; 98 | 99 | linkExprIdentity[foo[], bar[baz]] 100 | , 101 | {foo[], bar[baz]} 102 | ] 103 | 104 | TestMatch[ 105 | exprStringJoin = $functions["expr_string_join"]; 106 | 107 | { 108 | exprStringJoin[], 109 | exprStringJoin["Foo"], 110 | exprStringJoin["Foo", "Bar"], 111 | exprStringJoin[Sequence @@ CharacterRange["a", "f"]], 112 | exprStringJoin[1, 2, 3] 113 | } 114 | , 115 | { 116 | "", 117 | "Foo", 118 | "FooBar", 119 | "abcdef", 120 | Failure["RustPanic", <| 121 | "MessageTemplate" -> "Rust LibraryLink function panic: `message`", 122 | "MessageParameters" -> <|"message" -> "expected String argument, got: 1"|>, 123 | (* Avoid hard-coding the panic line/column number into the test. *) 124 | "SourceLocation" -> s_?StringQ /; StringStartsQ[s, "wolfram-library-link/examples/wstp.rs:"], 125 | "Backtrace" -> Missing["NotEnabled"] 126 | |>] 127 | } 128 | ] 129 | 130 | TestMatch[ 131 | total = $functions["total"]; 132 | 133 | { 134 | total[], 135 | total[1, 2, 3], 136 | total[1, 2.5, 7], 137 | (* Cause an integer overflow. *) 138 | total[2^62, 2^62, 2^62], 139 | total[5, "Hello"] 140 | } 141 | , 142 | { 143 | 0, 144 | 6, 145 | 10.5, 146 | Failure["RustPanic", <| 147 | "MessageTemplate" -> "Rust LibraryLink function panic: `message`", 148 | "MessageParameters" -> <|"message" -> "attempt to add with overflow"|>, 149 | "SourceLocation" -> s0_?StringQ /; StringStartsQ[s0, "wolfram-library-link/examples/wstp.rs:"], 150 | "Backtrace" -> Missing["NotEnabled"] 151 | |>], 152 | Failure["RustPanic", <| 153 | "MessageTemplate" -> "Rust LibraryLink function panic: `message`", 154 | "MessageParameters" -> <| 155 | "message" -> "expected argument at position 2 to be a number, got \"Hello\"" 156 | |>, 157 | "SourceLocation" -> s1_?StringQ /; StringStartsQ[s1, "wolfram-library-link/examples/wstp.rs:"], 158 | "Backtrace" -> Missing["NotEnabled"] 159 | |>] 160 | } 161 | ] -------------------------------------------------------------------------------- /wolfram-library-link/RustLink/LibraryResources/.include_in_git: -------------------------------------------------------------------------------- 1 | # This file exists because otherwise empty directories can't be saved to git. 2 | -------------------------------------------------------------------------------- /wolfram-library-link/RustLink/PacletInfo.m: -------------------------------------------------------------------------------- 1 | (* Paclet Info File *) 2 | 3 | (* created 2020/05/04*) 4 | 5 | PacletObject[<| 6 | "Name" -> "RustLink", 7 | "Version" -> "0.0.1", 8 | "MathematicaVersion" -> "12.3+", 9 | "Extensions" -> { 10 | {"Documentation", Language -> "English"}, 11 | {"LibraryLink"}, 12 | {"Tests", "Method" -> "Experimental-v1"}, 13 | {"Tests", "Method" -> "Experimental-v1", "Root" -> "Examples"}, 14 | 15 | (* Test the documentation examples used in wolfram_library_link::docs. *) 16 | {"Tests", "Method" -> "Experimental-v1", "Root" -> "Examples/Docs/Convert"}, 17 | {"Tests", "Method" -> "Experimental-v1", "Root" -> "Examples/Docs/EvaluateWolframCodeFromRust"} 18 | } 19 | |>] 20 | 21 | 22 | -------------------------------------------------------------------------------- /wolfram-library-link/RustLink/Tests/DataStore.wlt: -------------------------------------------------------------------------------- 1 | 2 | Needs["MUnit`"] 3 | 4 | Test[ 5 | func = LibraryFunctionLoad[ 6 | "liblibrary_tests", 7 | "test_empty_data_store", 8 | {}, 9 | "DataStore" 10 | ]; 11 | 12 | func[] 13 | , 14 | Developer`DataStore[] 15 | ] 16 | 17 | Test[ 18 | func = LibraryFunctionLoad[ 19 | "liblibrary_tests", 20 | "test_single_int_data_store", 21 | {}, 22 | "DataStore" 23 | ]; 24 | 25 | func[] 26 | , 27 | Developer`DataStore[1] 28 | ] 29 | 30 | Test[ 31 | func = LibraryFunctionLoad[ 32 | "liblibrary_tests", 33 | "test_multiple_int_data_store", 34 | {}, 35 | "DataStore" 36 | ]; 37 | 38 | func[] 39 | , 40 | Developer`DataStore[1, 2, 3] 41 | ] 42 | 43 | Test[ 44 | func = LibraryFunctionLoad[ 45 | "liblibrary_tests", 46 | "test_unnamed_heterogenous_data_store", 47 | {}, 48 | "DataStore" 49 | ]; 50 | 51 | func[] 52 | , 53 | Developer`DataStore[1, 2.0, "hello"] 54 | ] 55 | 56 | Test[ 57 | func = LibraryFunctionLoad[ 58 | "liblibrary_tests", 59 | "test_named_heterogenous_data_store", 60 | {}, 61 | "DataStore" 62 | ]; 63 | 64 | func[] 65 | , 66 | Developer`DataStore[ 67 | "an i64" -> 1, 68 | "an f64" -> 2.0, 69 | "a str" -> "hello" 70 | ] 71 | ] 72 | 73 | Test[ 74 | func = LibraryFunctionLoad[ 75 | "liblibrary_tests", 76 | "test_named_and_unnamed_heterogenous_data_store", 77 | {}, 78 | "DataStore" 79 | ]; 80 | 81 | func[] 82 | , 83 | Developer`DataStore[1, "real" -> 2.0, "hello" -> "world"] 84 | ] 85 | 86 | (*====================================*) 87 | (* Non-atomic types *) 88 | (*====================================*) 89 | 90 | Test[ 91 | func = LibraryFunctionLoad[ 92 | "liblibrary_tests", 93 | "test_named_numeric_array_data_store", 94 | {}, 95 | "DataStore" 96 | ]; 97 | 98 | func[] 99 | , 100 | Developer`DataStore[ 101 | "array" -> NumericArray[{1, 2, 3}, "Integer64"] 102 | ] 103 | ] 104 | 105 | Test[ 106 | func = LibraryFunctionLoad[ 107 | "liblibrary_tests", 108 | "test_nested_data_store", 109 | {}, 110 | "DataStore" 111 | ]; 112 | 113 | func[] 114 | , 115 | Developer`DataStore[ 116 | "is_inner" -> False, 117 | Developer`DataStore["is_inner" -> True] 118 | ] 119 | ] 120 | 121 | Test[ 122 | func = LibraryFunctionLoad[ 123 | "liblibrary_tests", 124 | "test_iterated_nested_data_store", 125 | {}, 126 | "DataStore" 127 | ]; 128 | 129 | func[] 130 | , 131 | Developer`DataStore[ 132 | Developer`DataStore[ 133 | Developer`DataStore[ 134 | Developer`DataStore["level" -> 0], 135 | "level" -> 1 136 | ], 137 | "level" -> 2 138 | ] 139 | ] 140 | ] 141 | 142 | (*====================================*) 143 | (* DataStore arguments *) 144 | (*====================================*) 145 | 146 | Test[ 147 | LibraryFunctionLoad[ 148 | "liblibrary_tests", 149 | "test_data_store_arg", 150 | {"DataStore"}, 151 | Integer 152 | ][ 153 | Developer`DataStore["a", "b", "c"] 154 | ] 155 | , 156 | 3 157 | ] 158 | 159 | (*====================================*) 160 | (* DataStore nodes *) 161 | (*====================================*) 162 | 163 | Test[ 164 | LibraryFunctionLoad[ 165 | "liblibrary_tests", 166 | "test_data_store_nodes", 167 | {}, 168 | "Void" 169 | ][] 170 | , 171 | Null 172 | ] -------------------------------------------------------------------------------- /wolfram-library-link/RustLink/Tests/Images.wlt: -------------------------------------------------------------------------------- 1 | Needs["MUnit`"] 2 | 3 | Test[ 4 | LibraryFunctionLoad[ 5 | "liblibrary_tests", 6 | "test_image_arg", 7 | {LibraryDataType[Image, "Bit"]}, 8 | NumericArray 9 | ][ 10 | Image[{{0, 1}, {1, 0}}, "Bit"] 11 | ] 12 | , 13 | NumericArray[{0, 1, 1, 0}, "Integer8"] 14 | ] 15 | 16 | Test[ 17 | LibraryFunctionLoad["liblibrary_tests", "test_create_bitmap_image", {}, Image][] 18 | , 19 | Image[{{0, 1}, {1, 0}}, "Bit"] 20 | ] 21 | 22 | Test[ 23 | LibraryFunctionLoad["liblibrary_tests", "test_create_color_rgb_u8_image", {}, Image][] 24 | , 25 | Image[ 26 | NumericArray[ 27 | { 28 | {{255, 0 }, { 0, 200}}, (* Red channel*) 29 | {{0, 255}, { 0, 200}}, (* Green channel *) 30 | {{0, 0}, {255, 200}} (* Blue channel *) 31 | }, 32 | "UnsignedInteger8" 33 | ], 34 | "Byte", 35 | ColorSpace -> "RGB", 36 | Interleaving -> False 37 | ] 38 | ] 39 | 40 | Test[ 41 | LibraryFunctionLoad["liblibrary_tests", "test_create_color_rgb_f32_image", {}, Image][] 42 | , 43 | Image[ 44 | NumericArray[ 45 | { 46 | {{1.0, 0.0}, {0.0, 0.8}}, (* Red channel*) 47 | {{0.0, 1.0}, {0.0, 0.8}}, (* Green channel *) 48 | {{0.0, 0.0}, {1.0, 0.8}} (* Blue channel *) 49 | }, 50 | "Real32" 51 | ], 52 | "Real32", 53 | ColorSpace -> "RGB", 54 | Interleaving -> False 55 | ] 56 | ] -------------------------------------------------------------------------------- /wolfram-library-link/RustLink/Tests/NativeArgs.wlt: -------------------------------------------------------------------------------- 1 | Needs["MUnit`"] 2 | 3 | Test[ 4 | LibraryFunctionLoad[ 5 | "liblibrary_tests", 6 | "test_no_args", 7 | {}, 8 | Integer 9 | ][] 10 | , 11 | 4 12 | ] 13 | 14 | Test[ 15 | LibraryFunctionLoad[ 16 | "liblibrary_tests", 17 | "test_ret_void", 18 | {}, 19 | "Void" 20 | ][] 21 | , 22 | Null 23 | ] 24 | 25 | Test[ 26 | LibraryFunctionLoad[ 27 | "liblibrary_tests", 28 | "test_mint", 29 | {Integer}, 30 | Integer 31 | ][5] 32 | , 33 | 25 34 | ] 35 | 36 | Test[ 37 | LibraryFunctionLoad[ 38 | "liblibrary_tests", 39 | "test_raw_mint", 40 | {Integer}, 41 | Integer 42 | ][9] 43 | , 44 | 81 45 | ] 46 | 47 | Test[ 48 | LibraryFunctionLoad[ 49 | "liblibrary_tests", 50 | "test_mint_mint", 51 | {Integer, Integer}, 52 | Integer 53 | ][5, 10] 54 | , 55 | 15 56 | ] 57 | 58 | Test[ 59 | LibraryFunctionLoad[ 60 | "liblibrary_tests", 61 | "test_mreal", 62 | {Real}, 63 | Real 64 | ][2.5] 65 | , 66 | 6.25 67 | ] 68 | 69 | Test[ 70 | LibraryFunctionLoad[ 71 | "liblibrary_tests", 72 | "test_i64", 73 | {Integer}, 74 | Integer 75 | ][5] 76 | , 77 | 25 78 | ] 79 | 80 | Test[ 81 | LibraryFunctionLoad[ 82 | "liblibrary_tests", 83 | "test_i64_i64", 84 | {Integer, Integer}, 85 | Integer 86 | ][5, 10] 87 | , 88 | 15 89 | ] 90 | 91 | Test[ 92 | LibraryFunctionLoad[ 93 | "liblibrary_tests", 94 | "test_f64", 95 | {Real}, 96 | Real 97 | ][2.5] 98 | , 99 | 6.25 100 | ] 101 | 102 | (*---------*) 103 | (* Strings *) 104 | (*---------*) 105 | 106 | (* Test[ 107 | LibraryFunctionLoad[ 108 | "liblibrary_tests", 109 | "test_str", 110 | {String}, 111 | String 112 | ]["hello"] 113 | , 114 | "olleh" 115 | ] *) 116 | 117 | Test[ 118 | LibraryFunctionLoad[ 119 | "liblibrary_tests", 120 | "test_string", 121 | {String}, 122 | String 123 | ]["hello"] 124 | , 125 | "olleh" 126 | ] 127 | 128 | Test[ 129 | LibraryFunctionLoad[ 130 | "liblibrary_tests", 131 | "test_c_string", 132 | {String}, 133 | Integer 134 | ]["hello world"] 135 | , 136 | 11 137 | ] 138 | 139 | (*---------*) 140 | (* Panics *) 141 | (*---------*) 142 | 143 | Test[ 144 | LibraryFunctionLoad[ 145 | "liblibrary_tests", 146 | "test_panic", 147 | {}, 148 | "Void" 149 | ][] 150 | , 151 | LibraryFunctionError["LIBRARY_USER_ERROR", 1002] 152 | , 153 | {LibraryFunction::rterr} 154 | ] 155 | 156 | (*----------------*) 157 | (* NumericArray's *) 158 | (*----------------*) 159 | 160 | Test[ 161 | totalI64 = LibraryFunctionLoad[ 162 | "liblibrary_tests", 163 | "total_i64", 164 | {LibraryDataType[NumericArray, "Integer64"]}, 165 | Integer 166 | ]; 167 | 168 | totalI64[NumericArray[Range[100], "Integer64"]] 169 | , 170 | 5050 171 | ] 172 | 173 | Test[ 174 | positiveQ = LibraryFunctionLoad[ 175 | "liblibrary_tests", 176 | "positive_i64", 177 | {LibraryDataType[NumericArray, "Integer64"]}, 178 | LibraryDataType[NumericArray, "UnsignedInteger8"] 179 | ]; 180 | 181 | positiveQ[NumericArray[{0, 1, -2, 3, 4, -5}, "Integer64"]] 182 | , 183 | NumericArray[{0, 1, 0, 1, 1, 0}, "UnsignedInteger8"] 184 | ] -------------------------------------------------------------------------------- /wolfram-library-link/RustLink/Tests/NumericArrayConversions.wlt: -------------------------------------------------------------------------------- 1 | Needs["MUnit`"] 2 | 3 | Test[ 4 | LibraryFunctionLoad["liblibrary_tests", "test_na_conversions", {}, "Void"][] 5 | , 6 | Null 7 | ] -------------------------------------------------------------------------------- /wolfram-library-link/RustLink/Tests/ShareCounts.wlt: -------------------------------------------------------------------------------- 1 | Needs["MUnit`"] 2 | 3 | $NAType = LibraryDataType[NumericArray, "Integer64"] 4 | (* Constructs a fresh NumericArray on each access. *) 5 | $NA := NumericArray[{1, 2, 3}, "Integer64"] 6 | 7 | Test[ 8 | LibraryFunctionLoad[ 9 | "liblibrary_tests", 10 | "test_na_automatic_count", 11 | { 12 | {LibraryDataType[NumericArray, "Integer64"], Automatic} 13 | }, 14 | Integer 15 | ][$NA] 16 | , 17 | 0 18 | ] 19 | 20 | Test[ 21 | LibraryFunctionLoad[ 22 | "liblibrary_tests", 23 | "test_na_constant_count", 24 | { 25 | {LibraryDataType[NumericArray, "Integer64"], "Constant"} 26 | }, 27 | Integer 28 | ][$NA] 29 | , 30 | 0 31 | ] 32 | 33 | Test[ 34 | LibraryFunctionLoad[ 35 | "liblibrary_tests", 36 | "test_na_manual_count", 37 | { 38 | {LibraryDataType[NumericArray, "Integer64"], "Manual"} 39 | }, 40 | Integer 41 | ][$NA] 42 | , 43 | 0 44 | ] 45 | 46 | Test[ 47 | LibraryFunctionLoad[ 48 | "liblibrary_tests", 49 | "test_na_shared_count", 50 | { 51 | {LibraryDataType[NumericArray, "Integer64"], "Shared"} 52 | }, 53 | Integer 54 | ][$NA] 55 | , 56 | 1 57 | ] 58 | 59 | (* Test passing one NumericArray as two different arguments, using "Constant". *) 60 | Test[ 61 | With[{array = $NA}, 62 | LibraryFunctionLoad[ 63 | "liblibrary_tests", 64 | "test_na_constant_are_ptr_eq", 65 | { 66 | {LibraryDataType[NumericArray, "Integer64"], "Constant"}, 67 | {LibraryDataType[NumericArray, "Integer64"], "Constant"} 68 | }, 69 | "DataStore" 70 | ][array, array] 71 | ] 72 | , 73 | (* The two arrays: 74 | * should be `ptr_eq()` 75 | * their `share_count()` should be 0 76 | *) 77 | Developer`DataStore[True, 0] 78 | ] 79 | 80 | (* Test passing one NumericArray as two different arguments, using "Manual". *) 81 | Test[ 82 | With[{array = $NA}, 83 | LibraryFunctionLoad[ 84 | "liblibrary_tests", 85 | "test_na_manual_are_not_ptr_eq", 86 | { 87 | {LibraryDataType[NumericArray, "Integer64"], "Manual"}, 88 | {LibraryDataType[NumericArray, "Integer64"], "Manual"} 89 | }, 90 | "DataStore" 91 | ][array, array] 92 | ] 93 | , 94 | (* The two arrays: 95 | * should *not* be `ptr_eq()` 96 | * their `share_count()` should be 0 97 | * `array1.as_slice_mut().is_some()` should be True 98 | *) 99 | Developer`DataStore[False, 0, True] 100 | ] 101 | 102 | (* Test passing one NumericArray as two different arguments, using "Shared". *) 103 | Test[ 104 | With[{array = $NA}, 105 | LibraryFunctionLoad[ 106 | "liblibrary_tests", 107 | "test_na_shared_are_ptr_eq", 108 | { 109 | {LibraryDataType[NumericArray, "Integer64"], "Shared"}, 110 | {LibraryDataType[NumericArray, "Integer64"], "Shared"} 111 | }, 112 | "DataStore" 113 | ][array, array] 114 | ] 115 | , 116 | (* The two arrays: 117 | * should be `ptr_eq()` 118 | * their `share_count()` should be 2 119 | * `array1.as_slice_mut().is_some()` should be False 120 | *) 121 | Developer`DataStore[True, 2, False] 122 | ] 123 | 124 | (* Test cloning a NumericArray *) 125 | Test[ 126 | LibraryFunctionLoad[ 127 | "liblibrary_tests", 128 | "test_na_clone", 129 | {}, 130 | "Boolean" 131 | ][] 132 | ] 133 | 134 | (* Test cloning a "Shared" NumericArray *) 135 | Test[ 136 | LibraryFunctionLoad[ 137 | "liblibrary_tests", 138 | "test_na_shared_clone", 139 | { 140 | {LibraryDataType[NumericArray, "Integer64"], "Shared"} 141 | }, 142 | "Boolean" 143 | ][$NA] 144 | ] -------------------------------------------------------------------------------- /wolfram-library-link/RustLink/Tests/Threading.wlt: -------------------------------------------------------------------------------- 1 | Needs["MUnit`"] 2 | 3 | Test[ 4 | Block[{$Context = "UnlikelyContext`", $ContextPath = {}}, 5 | LibraryFunctionLoad[ 6 | "liblibrary_tests", "test_runtime_function_from_main_thread", {}, "Boolean" 7 | ][] 8 | ] 9 | , 10 | True 11 | ] 12 | 13 | Test[ 14 | result = Block[{$Context = "UnlikelyContext`", $ContextPath = {}}, 15 | LibraryFunctionLoad[ 16 | "liblibrary_tests", "test_runtime_function_from_non_main_thread", {}, String 17 | ][] 18 | ]; 19 | 20 | StringQ[result] && StringStartsQ[ 21 | result, 22 | "PANIC: error: attempted to call back into the Wolfram Kernel from a non-main thread at" 23 | ] 24 | ] -------------------------------------------------------------------------------- /wolfram-library-link/RustLink/Tests/WSTP.wlt: -------------------------------------------------------------------------------- 1 | Needs["MUnit`"] 2 | 3 | TestMatch[ 4 | LibraryFunctionLoad[ 5 | "liblibrary_tests", 6 | "test_wstp_fn_empty", 7 | LinkObject, 8 | LinkObject 9 | ][] 10 | , 11 | (* The empty arguments list is never read, so it's left on the link and assumed to be 12 | the return value. *) 13 | {} 14 | ] 15 | 16 | TestMatch[ 17 | LibraryFunctionLoad[ 18 | "liblibrary_tests", 19 | "test_wstp_fn_panic_immediately", 20 | LinkObject, 21 | LinkObject 22 | ][] 23 | , 24 | Failure["RustPanic", <| 25 | "MessageTemplate" -> "Rust LibraryLink function panic: `message`", 26 | "MessageParameters" -> <|"message" -> "successful panic"|>, 27 | (* Avoid hard-coding the panic line/column number into the test. *) 28 | "SourceLocation" -> s_?StringQ /; StringStartsQ[s, "wolfram-library-link/examples/tests/test_wstp.rs:"], 29 | "Backtrace" -> Missing["NotEnabled"] 30 | |>] 31 | ] 32 | 33 | TestMatch[ 34 | LibraryFunctionLoad[ 35 | "liblibrary_tests", 36 | "test_wstp_fn_panic_immediately_with_formatting", 37 | LinkObject, 38 | LinkObject 39 | ][] 40 | , 41 | Failure["RustPanic", <| 42 | "MessageTemplate" -> "Rust LibraryLink function panic: `message`", 43 | "MessageParameters" -> <|"message" -> "successful formatted panic"|>, 44 | (* Avoid hard-coding the panic line/column number into the test. *) 45 | "SourceLocation" -> s_?StringQ /; StringStartsQ[s, "wolfram-library-link/examples/tests/test_wstp.rs:"], 46 | "Backtrace" -> Missing["NotEnabled"] 47 | |>] 48 | ] 49 | 50 | TestMatch[ 51 | LibraryFunctionLoad[ 52 | "liblibrary_tests", 53 | "test_wstp_fn_panic_partial_result", 54 | LinkObject, 55 | LinkObject 56 | ][] 57 | , 58 | Failure["RustPanic", <| 59 | "MessageTemplate" -> "Rust LibraryLink function panic: `message`", 60 | "MessageParameters" -> <|"message" -> "incomplete result"|>, 61 | (* Avoid hard-coding the panic line/column number into the test. *) 62 | "SourceLocation" -> s_?StringQ /; StringStartsQ[s, "wolfram-library-link/examples/tests/test_wstp.rs:"], 63 | "Backtrace" -> Missing["NotEnabled"] 64 | |>] 65 | ] 66 | 67 | TestMatch[ 68 | LibraryFunctionLoad[ 69 | "liblibrary_tests", 70 | "test_wstp_fn_return_partial_result", 71 | LinkObject, 72 | LinkObject 73 | ][] 74 | , 75 | Unevaluated @ LibraryFunction[ 76 | s_String /; StringEndsQ[ 77 | s, 78 | FileNameJoin[{"RustLink", "LibraryResources", $SystemID}] 79 | ~~ $PathnameSeparator 80 | ~~ RepeatedNull["lib", 1] 81 | ~~ "library_tests." 82 | ~~ ("dylib" | "dll" | "so") 83 | ] 84 | , 85 | "test_wstp_fn_return_partial_result" 86 | , 87 | LinkObject 88 | ][] 89 | ] 90 | 91 | TestMatch[ 92 | LibraryFunctionLoad[ 93 | "liblibrary_tests", 94 | "test_wstp_fn_poison_link_and_panic", 95 | LinkObject, 96 | LinkObject 97 | ][] 98 | , 99 | Failure["RustPanic", <| 100 | "MessageTemplate" -> "Rust LibraryLink function panic: `message`", 101 | "MessageParameters" -> <|"message" -> "successful panic"|>, 102 | (* Avoid hard-coding the panic line/column number into the test. *) 103 | "SourceLocation" -> s_?StringQ /; StringStartsQ[s, "wolfram-library-link/examples/tests/test_wstp.rs:"], 104 | "Backtrace" -> Missing["NotEnabled"] 105 | |>] 106 | ] 107 | 108 | TestMatch[ 109 | LibraryFunctionLoad[ 110 | "liblibrary_tests", 111 | "test_wstp_panic_with_empty_link", 112 | LinkObject, 113 | LinkObject 114 | ][] 115 | , 116 | Failure["RustPanic", <| 117 | "MessageTemplate" -> "Rust LibraryLink function panic: `message`", 118 | "MessageParameters" -> <|"message" -> "panic while !link.is_ready()"|>, 119 | "SourceLocation" -> s_?StringQ /; StringStartsQ[s, "wolfram-library-link/examples/tests/test_wstp.rs:"], 120 | "Backtrace" -> Missing["NotEnabled"] 121 | |>] 122 | ] 123 | 124 | (*====================================*) 125 | (* Vec *) 126 | (*====================================*) 127 | 128 | TestMatch[ 129 | Block[{$Context = "UnusedContext`", $ContextPath = {}}, 130 | LibraryFunctionLoad[ 131 | "liblibrary_tests", 132 | "test_wstp_expr_return_null", 133 | LinkObject, 134 | LinkObject 135 | ][] 136 | ] 137 | , 138 | Null 139 | ] -------------------------------------------------------------------------------- /wolfram-library-link/docs/Overview.md: -------------------------------------------------------------------------------- 1 | # Functionality Overview 2 | 3 | ## Function Types 4 | 5 | #### Native functions 6 | 7 | #### WSTP functions 8 | 9 | #### TODO: Expr functions 10 | 11 | ## WSTP Functions 12 | 13 | ### Symbol contexts problem 14 | 15 |
16 | 17 | Background 18 | 19 | 20 | In the Wolfram Language, a symbol is made up of two parts: a context, and a symbol name. 21 | For example, in the the symbol `` System`Plot ``, `` System` `` is the context, and `Plot` 22 | is the symbol name. The context denotes a collection of related symbols. For example, all 23 | symbols that are part of the core Wolfram Language are in the ``"System`"`` context; when 24 | you start a new Wolfram Language session, the default context that new symbols get created 25 | in is called ``"Global`"``; and so on. However, you won't often see symbol contexts 26 | written out explicitly in a Wolfram Language program. Instead, when a symbol name is 27 | entered, the system looks up that symbol name in the contexts listed in the 28 | [`$ContextPath`][ref/$ContextPath]: if a symbol with that name exists in one of the 29 | listed contexts, then the symbol name the user entered resolves to that context. 30 | 31 | So, for example, if the user enters the symbol name `Plot`, and ``"System`"`` is on 32 | `$ContextPath`, the system deduces the user was referring to the symbol ``System`Plot``. 33 | In this way, `$ContextPath` allows the user to user shorter symbol names to refer to 34 | symbols, and avoid having to write out the full context + symbol name as input. 35 | 36 | This shortening also works when printing symbols. For example, doing `ToString[Plot]` 37 | doesn't return ``"System`Plot"``, but rather just `"Plot"`. And herein lies the problem 38 | for WSTP functions. 39 |
40 | 41 | When an expression is sent across a WSTP link, symbols whose contexts are equal to 42 | [`$Context`][ref/$Context] or on [`$ContextPath`][ref/$ContextPath] will be sent as 43 | strings without the symbol context. 44 | 45 | This is a problem because, within Rust code, a symbol name without a context is ambiguous. 46 | If Rust code is expecting to get the symbol ``MyPackage`foo``, but recieves just `foo` 47 | over the WSTP link, there is no easy way for it to tell if that `foo` came from the symbol 48 | it expected, or e.g. ``MyOtherPackage`foo``. 49 | 50 | #### The solution 51 | 52 | When calling a WSTP function that parses the incoming arguments using the 53 | [`Expr`][crate::expr::Expr] type in some way (e.g. by calling `Link::get_expr()`), use 54 | the following idiom: 55 | 56 | ```wolfram 57 | (* func: LibraryFunction[_, _, LinkObject, LinkObject] *) 58 | 59 | Block[{$Context = "UnusedContext`", $ContextPath = {}}, 60 | func[arg1, arg2, ...] 61 | ] 62 | ``` 63 | 64 | Setting `$Context` and `$ContextPath` in this way will force symbols written to the 65 | function `Link` object to explicitly include their context. 66 | 67 | ### Panic `Failure[..]`s 68 | 69 | When a WSTP function [panics][panics], a [`Failure["RustPanic", ...]`][ref/Failure] object 70 | will be returned to the Wolfram Language. 71 | 72 | [panics]: https://doc.rust-lang.org/std/macro.panic.html 73 | [ref/Failure]: https://reference.wolfram.com/language/ref/Failure.html 74 | 75 | ##### Example 76 | 77 | ```rust 78 | # mod scope { 79 | use wolfram_library_link::{self as wll, wstp::Link}; 80 | 81 | #[wll::export(wstp)] 82 | fn sqrt(link: &mut Link) { 83 | let arg_count = link.test_head("List").unwrap(); 84 | 85 | if arg_count != 1 { 86 | panic!("expected 1 argument, got {}", arg_count); 87 | } 88 | 89 | let arg = link.get_f64().expect("expected Real argument"); 90 | 91 | if arg.is_negative() { 92 | panic!("cannot get the square root of a negative number"); 93 | } 94 | 95 | let value = arg.sqrt(); 96 | 97 | link.put_f64(value).unwrap() 98 | } 99 | # } 100 | ``` 101 | 102 | ```wolfram 103 | sqrt = LibraryFunctionLoad["...", "sqrt", LinkObject, LinkObject]; 104 | 105 | sqrt[] 106 | 107 | (* Returns: 108 | Failure["RustPanic", <| 109 | "MessageTemplate" -> "Rust LibraryLink function panic: `message`", 110 | "MessageParameters" -> <| "message" -> "expected 1 argument, got 0" |>, 111 | "SourceLocation" -> "<...>", 112 | "Backtrace" -> Missing["NotEnabled"] 113 | >] 114 | *) 115 | ``` 116 | 117 | #### Backtraces 118 | 119 | `wolfram-library-link` can optionally capture a stack backtrace for panics that occur 120 | within Rust LibraryLink functions. This behavior is disabled by default, but can be 121 | enabled by setting the `LIBRARY_LINK_RUST_BACKTRACE` environment variable to `"True"`: 122 | 123 | ```wolfram 124 | SetEnvironment["LIBRARY_LINK_RUST_BACKTRACE" -> "True"] 125 | ``` 126 | 127 | ##### Note on panic hooks 128 | 129 | `wolfram-library-link` captures information about panics by setting a custom Rust 130 | [panic hook](https://doc.rust-lang.org/std/panic/fn.set_hook.html). 131 | 132 | 133 | 134 | 135 | 136 | 137 | [ref/$Context]: https://reference.wolfram.com/language/ref/$Context.html 138 | [ref/$ContextPath]: https://reference.wolfram.com/language/ref/$ContextPath.html -------------------------------------------------------------------------------- /wolfram-library-link/docs/README.md: -------------------------------------------------------------------------------- 1 | The files in this directory are included directly in the `wolfram-library-link` crate 2 | documentation using: 3 | 4 | ```rust 5 | #[doc = include_str!("<..>/docs/included/.md")] 6 | ``` 7 | 8 | When viewed as markdown files in an online repository file browser, intra-doc links will 9 | not work correctly. -------------------------------------------------------------------------------- /wolfram-library-link/examples/aborts.rs: -------------------------------------------------------------------------------- 1 | use wolfram_library_link as wll; 2 | 3 | /// This function will execute forever until a Wolfram Language abort occurs. 4 | #[wll::export] 5 | fn wait_for_abort() { 6 | loop { 7 | if wll::aborted() { 8 | return; 9 | } 10 | 11 | std::thread::yield_now(); 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /wolfram-library-link/examples/async/async_file_watcher.rs: -------------------------------------------------------------------------------- 1 | use std::{ 2 | fs, 3 | path::PathBuf, 4 | time::{Duration, SystemTime}, 5 | }; 6 | 7 | use wolfram_library_link::{self as wll, sys::mint, AsyncTaskObject, DataStore}; 8 | 9 | /// Start an asynchronous task that will watch for modifications to a file. 10 | /// 11 | /// See `RustLink/Tests/AsyncExamples.wlt` for example usage of this function. 12 | #[wll::export] 13 | fn start_file_watcher(pause_interval_ms: mint, path: String) -> mint { 14 | let pause_interval_ms = 15 | u64::try_from(pause_interval_ms).expect("mint interval overflows u64"); 16 | 17 | let path = PathBuf::from(path); 18 | 19 | // Spawn a new thread, which will run in the background and check for file 20 | // modifications. 21 | let task = AsyncTaskObject::spawn_with_thread(move |task: AsyncTaskObject| { 22 | file_watch_thread_function(task, pause_interval_ms, &path); 23 | }); 24 | 25 | task.id() 26 | } 27 | 28 | /// This function is called from the spawned background thread. 29 | fn file_watch_thread_function( 30 | task: AsyncTaskObject, 31 | pause_interval_ms: u64, 32 | path: &PathBuf, 33 | ) { 34 | let mut prev_changed: Option = fs::metadata(path) 35 | .and_then(|metadata| metadata.modified()) 36 | .ok(); 37 | 38 | // Stateful closure which checks if the file at `path` has been modified since the 39 | // last time this closure was called (and `prev_changed was updated). Using a closure 40 | // simplifies the control flow in the main `loop` below, which should sleep on every 41 | // iteration regardless of how this function returns. 42 | let mut check_for_modification = || -> Option<_> { 43 | let changed: Option = fs::metadata(path).ok(); 44 | 45 | let notify: Option = match (&prev_changed, changed) { 46 | (Some(prev), Some(latest)) => { 47 | let latest: SystemTime = match latest.modified() { 48 | Ok(latest) => latest, 49 | Err(_) => return None, 50 | }; 51 | 52 | if *prev != latest { 53 | prev_changed = Some(latest.clone()); 54 | Some(latest) 55 | } else { 56 | None 57 | } 58 | }, 59 | // TODO: Notify on file removal? 60 | (Some(_prev), None) => None, 61 | (None, Some(latest)) => latest.modified().ok(), 62 | (None, None) => None, 63 | }; 64 | 65 | let time = notify?; 66 | 67 | let since_epoch = match time.duration_since(std::time::UNIX_EPOCH) { 68 | Ok(duration) => duration, 69 | Err(_) => return None, 70 | }; 71 | 72 | let since_epoch = since_epoch.as_secs(); 73 | 74 | Some(since_epoch) 75 | }; 76 | 77 | loop { 78 | if !task.is_alive() { 79 | break; 80 | } 81 | 82 | // Check to see if the file has been modified. If it has, raise an async event 83 | // called "change", and attach the modification timestamp as event data. 84 | if let Some(modification) = check_for_modification() { 85 | let mut data = DataStore::new(); 86 | data.add_i64(modification as i64); 87 | 88 | task.raise_async_event("change", data); 89 | } 90 | 91 | // Wait for a bit before polling again for any changes to the file. 92 | std::thread::sleep(Duration::from_millis(pause_interval_ms)); 93 | } 94 | } 95 | -------------------------------------------------------------------------------- /wolfram-library-link/examples/async/async_file_watcher_raw.rs: -------------------------------------------------------------------------------- 1 | use std::{ 2 | ffi::CStr, 3 | fs, 4 | os::raw::{c_uint, c_void}, 5 | path::PathBuf, 6 | time::{Duration, SystemTime}, 7 | }; 8 | 9 | use wolfram_library_link::{ 10 | self as wll, rtl, 11 | sys::{self, mint, MArgument, LIBRARY_FUNCTION_ERROR, LIBRARY_NO_ERROR}, 12 | }; 13 | 14 | struct FileWatcherArgs { 15 | pause_interval_ms: u64, 16 | path: PathBuf, 17 | } 18 | 19 | /// Start an asynchronous task that will watch for modifications to a file. 20 | /// 21 | /// See `RustLink/Tests/AsyncExamples.wlt` for example usage of this function. 22 | #[no_mangle] 23 | pub extern "C" fn start_file_watcher( 24 | lib_data: sys::WolframLibraryData, 25 | arg_count: mint, 26 | args: *mut MArgument, 27 | res: MArgument, 28 | ) -> c_uint { 29 | let args = unsafe { std::slice::from_raw_parts(args, arg_count as usize) }; 30 | 31 | if args.len() != 2 { 32 | return LIBRARY_FUNCTION_ERROR; 33 | } 34 | 35 | if unsafe { wll::initialize(lib_data) }.is_err() { 36 | return LIBRARY_FUNCTION_ERROR; 37 | } 38 | 39 | let task_arg = unsafe { 40 | FileWatcherArgs { 41 | pause_interval_ms: u64::try_from(*args[0].integer) 42 | .expect("i64 interval overflows u64"), 43 | path: { 44 | let cstr = CStr::from_ptr(*args[1].utf8string); 45 | match cstr.to_str() { 46 | Ok(s) => PathBuf::from(s), 47 | Err(_) => return LIBRARY_FUNCTION_ERROR, 48 | } 49 | }, 50 | } 51 | }; 52 | 53 | // FIXME: This box is being leaked. Where is an appropriate place to drop it? 54 | let task_arg = Box::into_raw(Box::new(task_arg)) as *mut c_void; 55 | 56 | // Spawn a new thread, which will run in the background and check for file 57 | // modifications. 58 | unsafe { 59 | let task_id: mint = wll::rtl::createAsynchronousTaskWithThread( 60 | Some(file_watch_thread_function), 61 | task_arg, 62 | ); 63 | *res.integer = task_id; 64 | } 65 | 66 | LIBRARY_NO_ERROR 67 | } 68 | 69 | /// This function is called from the spawned background thread. 70 | extern "C" fn file_watch_thread_function(async_object_id: mint, task_arg: *mut c_void) { 71 | let task_arg = task_arg as *mut FileWatcherArgs; 72 | let task_arg: &FileWatcherArgs = unsafe { &*task_arg }; 73 | 74 | let FileWatcherArgs { 75 | pause_interval_ms, 76 | ref path, 77 | } = *task_arg; 78 | 79 | let mut prev_changed: Option = fs::metadata(path) 80 | .and_then(|metadata| metadata.modified()) 81 | .ok(); 82 | 83 | // Stateful closure which checks if the file at `path` has been modified since the 84 | // last time this closure was called (and `prev_changed was updated). Using a closure 85 | // simplifies the control flow in the main `loop` below, which should sleep on every 86 | // iteration regardless of how this function returns. 87 | let mut check_for_modification = || -> Option<_> { 88 | let changed: Option = fs::metadata(path).ok(); 89 | 90 | let notify: Option = match (&prev_changed, changed) { 91 | (Some(prev), Some(latest)) => { 92 | let latest: SystemTime = match latest.modified() { 93 | Ok(latest) => latest, 94 | Err(_) => return None, 95 | }; 96 | 97 | if *prev != latest { 98 | prev_changed = Some(latest.clone()); 99 | Some(latest) 100 | } else { 101 | None 102 | } 103 | }, 104 | // TODO: Notify on file removal? 105 | (Some(_prev), None) => None, 106 | (None, Some(latest)) => latest.modified().ok(), 107 | (None, None) => None, 108 | }; 109 | 110 | let time = notify?; 111 | 112 | let since_epoch = match time.duration_since(std::time::UNIX_EPOCH) { 113 | Ok(duration) => duration, 114 | Err(_) => return None, 115 | }; 116 | 117 | let since_epoch = since_epoch.as_secs(); 118 | 119 | Some(since_epoch) 120 | }; 121 | 122 | loop { 123 | if unsafe { rtl::asynchronousTaskAliveQ(async_object_id) } == 0 { 124 | break; 125 | } 126 | 127 | // Check to see if the file has been modified. If it has, raise an async event 128 | // called "change", and attach the modification timestamp as event data. 129 | if let Some(modification) = check_for_modification() { 130 | unsafe { 131 | let data_store: sys::DataStore = rtl::createDataStore(); 132 | rtl::DataStore_addInteger(data_store, modification as i64); 133 | 134 | rtl::raiseAsyncEvent( 135 | async_object_id, 136 | "change\0".as_ptr() as *mut _, 137 | data_store, 138 | ) 139 | } 140 | } 141 | 142 | // Wait for a bit before polling again for any changes to the file. 143 | std::thread::sleep(Duration::from_millis(pause_interval_ms)); 144 | } 145 | } 146 | -------------------------------------------------------------------------------- /wolfram-library-link/examples/basic_types.rs: -------------------------------------------------------------------------------- 1 | //! This example demonstrates how LibraryLink native data types can be used in Rust 2 | //! functions called via LibraryLink. 3 | 4 | use wolfram_library_link::{self as wll, NumericArray, UninitNumericArray}; 5 | 6 | wll::generate_loader!(load_basic_types_functions); 7 | 8 | //====================================== 9 | // Primitive data types 10 | //====================================== 11 | 12 | //--------- 13 | // square() 14 | //--------- 15 | 16 | /// Define a function to square a number. 17 | /// 18 | /// The exported LibraryLink function may be loaded and used by evaluating: 19 | /// 20 | /// ```wolfram 21 | /// square = LibraryFunctionLoad["libbasic_types", "square", {Integer}, Integer]; 22 | /// square[2] 23 | /// ``` 24 | // 25 | // Export the `square` function via LibraryLink. This will generate a "wrapper" function 26 | // that correctly implements the lightweight LibraryLink <=> Rust conversion. 27 | #[wll::export] 28 | fn square(x: i64) -> i64 { 29 | x * x 30 | } 31 | 32 | //----------------- 33 | // reverse_string() 34 | //----------------- 35 | 36 | #[wll::export] 37 | fn reverse_string(string: String) -> String { 38 | string.chars().rev().collect() 39 | } 40 | 41 | //------------------ 42 | // add2() and add3() 43 | //------------------ 44 | 45 | #[wll::export] 46 | fn add2(x: i64, y: i64) -> i64 { 47 | x + y 48 | } 49 | 50 | #[wll::export] 51 | fn add3(x: i64, y: i64, z: i64) -> i64 { 52 | x + y + z 53 | } 54 | 55 | //====================================== 56 | // NumericArray's 57 | //====================================== 58 | 59 | //------------ 60 | // total_i64() 61 | //------------ 62 | 63 | // Load and use by evaluating: 64 | // 65 | // ```wolfram 66 | // total = LibraryFunctionLoad[ 67 | // "libbasic_types", 68 | // "total_i64", 69 | // {LibraryDataType[NumericArray, "Integer64"]}, 70 | // Integer 71 | // ]; 72 | // 73 | // total[NumericArray[Range[100], "Integer64"]] 74 | // ``` 75 | #[wll::export] 76 | fn total_i64(list: &NumericArray) -> i64 { 77 | list.as_slice().into_iter().sum() 78 | } 79 | 80 | //--------------- 81 | // positive_i64() 82 | //--------------- 83 | 84 | /// Get the sign of every element in `list` as a numeric array of 0's and 1's. 85 | /// 86 | /// The returned array will have the same dimensions as `list`. 87 | #[wll::export] 88 | fn positive_i64(list: &NumericArray) -> NumericArray { 89 | let mut bools: UninitNumericArray = 90 | UninitNumericArray::from_dimensions(list.dimensions()); 91 | 92 | for pair in list.as_slice().into_iter().zip(bools.as_slice_mut()) { 93 | let (elem, entry): (&i64, &mut std::mem::MaybeUninit) = pair; 94 | 95 | entry.write(u8::from(elem.is_positive())); 96 | } 97 | 98 | unsafe { bools.assume_init() } 99 | } 100 | 101 | //====================================== 102 | // get_random_number() 103 | //====================================== 104 | 105 | // Load and use by evaluating: 106 | // 107 | // ```wolfram 108 | // randomNumber = LibraryFunctionLoad[ 109 | // "libbasic_types", 110 | // "xkcd_get_random_number", 111 | // {}, 112 | // Integer 113 | // ]; 114 | // randomNumber[] 115 | // ``` 116 | #[wll::export(name = "xkcd_get_random_number")] 117 | fn get_random_number() -> i64 { 118 | // chosen by fair dice roll. 119 | // guaranteed to be random. 120 | // xkcd.com/221 121 | 4 122 | } 123 | 124 | //====================================== 125 | // raw_square() 126 | //====================================== 127 | 128 | #[wll::export] 129 | fn raw_square(args: &[wll::sys::MArgument], ret: wll::sys::MArgument) { 130 | if args.len() != 1 { 131 | panic!("unexpected number of arguments"); 132 | } 133 | 134 | let x: i64 = unsafe { *args[0].integer }; 135 | 136 | unsafe { 137 | *ret.integer = x * x; 138 | } 139 | } 140 | -------------------------------------------------------------------------------- /wolfram-library-link/examples/data_store.rs: -------------------------------------------------------------------------------- 1 | //! This example demonstrates how the *LibraryLink* "DataStore" type can be used to pass 2 | //! expression-like data structures to and from Rust. 3 | //! 4 | //! The "DataStore" can be manipulated natively from Rust code, and is serialized to 5 | //! an expression structure that looks like: 6 | //! 7 | //! ```wolfram 8 | //! Developer`DataStore[1, "Hello, World!", "key" -> -12.5] 9 | //! ``` 10 | //! 11 | //! A "DataStore" contains a sequence of values, which may differ in type, and each 12 | //! value may optionally be named. 13 | 14 | use wolfram_library_link::{self as wll, DataStore, DataStoreNodeValue}; 15 | 16 | //-------------- 17 | // string_join() 18 | //-------------- 19 | 20 | /// Join the strings in a `DataStore`. 21 | /// 22 | /// This function may be called by evaluating: 23 | /// 24 | /// ```wolfram 25 | /// stringJoin = LibraryFunctionLoad["libdata_store", "string_join", {"DataStore"}, "String"]; 26 | /// 27 | /// (* Evaluates to: "hello world" *) 28 | /// stringJoin[Developer`DataStore["hello", " ", "world"]] 29 | /// ``` 30 | #[wll::export] 31 | fn string_join(store: DataStore) -> String { 32 | let mut buffer = String::new(); 33 | 34 | for node in store.nodes() { 35 | // If `node.value()` is a string, append it to our string. 36 | // If `node.value()` is NOT a string, silently skip it. 37 | if let DataStoreNodeValue::Str(string) = node.value() { 38 | buffer.push_str(string); 39 | } 40 | } 41 | 42 | buffer 43 | } 44 | -------------------------------------------------------------------------------- /wolfram-library-link/examples/docs/README.md: -------------------------------------------------------------------------------- 1 | Examples from the `wolfram_library_link::docs` module. 2 | -------------------------------------------------------------------------------- /wolfram-library-link/examples/docs/convert/manual_wstp.rs: -------------------------------------------------------------------------------- 1 | use wolfram_library_link::{ 2 | export, 3 | wstp::{self, Link}, 4 | }; 5 | 6 | struct Point { 7 | x: f64, 8 | y: f64, 9 | } 10 | 11 | #[export(wstp)] 12 | fn create_point(link: &mut Link) { 13 | // Assert that no arguments were given. 14 | assert_eq!(link.test_head("List"), Ok(0)); 15 | 16 | let point = Point { x: 1.0, y: 2.0 }; 17 | 18 | point.put(link).unwrap(); 19 | } 20 | 21 | impl Point { 22 | fn put(&self, link: &mut Link) -> Result<(), wstp::Error> { 23 | let Point { x, y } = *self; 24 | 25 | // Point[{x, y}] 26 | link.put_function("System`Point", 1)?; 27 | link.put_function("System`List", 2)?; 28 | link.put_f64(x)?; 29 | link.put_f64(y)?; 30 | 31 | Ok(()) 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /wolfram-library-link/examples/docs/convert/using_expr.rs: -------------------------------------------------------------------------------- 1 | use wolfram_library_link::{ 2 | export, 3 | expr::{Expr, Symbol}, 4 | }; 5 | 6 | struct Point { 7 | x: f64, 8 | y: f64, 9 | } 10 | 11 | #[export(wstp)] 12 | fn create_point2(args: Vec) -> Expr { 13 | assert!(args.is_empty()); 14 | 15 | let point = Point { x: 3.0, y: 4.0 }; 16 | 17 | point.to_expr() 18 | } 19 | 20 | impl Point { 21 | fn to_expr(&self) -> Expr { 22 | let Point { x, y } = *self; 23 | 24 | Expr::normal(Symbol::new("System`Point"), vec![Expr::list(vec![ 25 | Expr::real(x), 26 | Expr::real(y), 27 | ])]) 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /wolfram-library-link/examples/docs/evaluate_wolfram_code_from_rust/generate_message.rs: -------------------------------------------------------------------------------- 1 | use wolfram_library_link::{ 2 | self as wll, export, 3 | expr::{Expr, Symbol}, 4 | }; 5 | 6 | #[export(wstp)] 7 | fn generate_message(_: Vec) { 8 | // Construct the expression `Message[MySymbol::msg, "..."]`. 9 | let message = Expr::normal(Symbol::new("System`Message"), vec![ 10 | // MySymbol::msg is MessageName[MySymbol, "msg"] 11 | Expr::normal(Symbol::new("System`MessageName"), vec![ 12 | Expr::from(Symbol::new("Global`MySymbol")), 13 | Expr::string("msg"), 14 | ]), 15 | Expr::string("a Rust LibraryLink function"), 16 | ]); 17 | 18 | // Evaluate the message expression. 19 | let _: Expr = wll::evaluate(&message); 20 | } 21 | -------------------------------------------------------------------------------- /wolfram-library-link/examples/docs/main.rs: -------------------------------------------------------------------------------- 1 | mod convert { 2 | mod manual_wstp; 3 | mod using_expr; 4 | } 5 | 6 | mod evaluate_wolfram_code_from_rust { 7 | mod generate_message; 8 | } 9 | -------------------------------------------------------------------------------- /wolfram-library-link/examples/exprs/basic_expressions.rs: -------------------------------------------------------------------------------- 1 | use wolfram_library_link::{ 2 | self as wll, 3 | expr::{Expr, Symbol}, 4 | }; 5 | 6 | /// This function is loaded by evaluating: 7 | /// 8 | /// ```wolfram 9 | /// LibraryFunctionLoad[ 10 | /// "/path/to/libbasic_expressions.dylib", 11 | /// "echo_arguments", 12 | /// LinkObject, 13 | /// LinkObject 14 | /// ] 15 | /// ``` 16 | #[wll::export(wstp)] 17 | pub fn echo_arguments(args: Vec) -> Expr { 18 | let arg_count = args.len(); 19 | 20 | for arg in args { 21 | // Echo[] 22 | wll::evaluate(&Expr::normal(Symbol::new("System`Echo"), vec![arg])); 23 | } 24 | 25 | Expr::string(format!("finished echoing {} argument(s)", arg_count)) 26 | } 27 | -------------------------------------------------------------------------------- /wolfram-library-link/examples/exprs/managed.rs: -------------------------------------------------------------------------------- 1 | use std::{collections::HashMap, sync::Mutex}; 2 | 3 | use once_cell::sync::Lazy; 4 | 5 | use wolfram_library_link::{ 6 | self as wll, 7 | expr::{Expr, ExprKind, Symbol}, 8 | managed::{Id, ManagedExpressionEvent}, 9 | }; 10 | 11 | wll::generate_loader![load_managed_exprs_functions]; 12 | 13 | /// Storage for all instances of [`MyObject`] associated with managed expressions 14 | /// created using `CreateManagedLibraryExpression`. 15 | static INSTANCES: Lazy>> = 16 | Lazy::new(|| Mutex::new(HashMap::new())); 17 | 18 | #[derive(Clone)] 19 | struct MyObject { 20 | value: String, 21 | } 22 | 23 | #[wll::init] 24 | fn init() { 25 | // Register `manage_instance()` as the handler for managed expressions created using: 26 | // 27 | // CreateManagedLibraryExpression["my_object", _] 28 | wll::managed::register_library_expression_manager("my_object", manage_instance); 29 | } 30 | 31 | fn manage_instance(action: ManagedExpressionEvent) { 32 | let mut instances = INSTANCES.lock().unwrap(); 33 | 34 | match action { 35 | ManagedExpressionEvent::Create(id) => { 36 | // Insert a new MyObject instance with some default values. 37 | instances.insert(id, MyObject { 38 | value: String::from("default"), 39 | }); 40 | }, 41 | ManagedExpressionEvent::Drop(id) => { 42 | if let Some(obj) = instances.remove(&id) { 43 | drop(obj); 44 | } 45 | }, 46 | } 47 | } 48 | 49 | /// Set the `MyObject.value` field for the specified instance ID. 50 | #[wll::export(wstp)] 51 | fn set_instance_value(args: Vec) { 52 | assert!(args.len() == 2, "set_instance_value: expected 2 arguments"); 53 | 54 | let id: u32 = unwrap_id_arg(&args[0]); 55 | let value: String = match args[1].kind() { 56 | ExprKind::String(str) => str.clone(), 57 | _ => panic!("expected 2nd argument to be a String, got: {}", args[1]), 58 | }; 59 | 60 | let mut instances = INSTANCES.lock().unwrap(); 61 | 62 | let instance: &mut MyObject = 63 | instances.get_mut(&id).expect("instance does not exist"); 64 | 65 | instance.value = value; 66 | } 67 | 68 | /// Get the fields of the `MyObject` instance for the specified instance ID. 69 | #[wll::export(wstp)] 70 | fn get_instance_data(args: Vec) -> Expr { 71 | assert!(args.len() == 1, "get_instance_data: expected 1 argument"); 72 | 73 | let id: u32 = unwrap_id_arg(&args[0]); 74 | 75 | let MyObject { value } = { 76 | let instances = INSTANCES.lock().unwrap(); 77 | 78 | instances 79 | .get(&id) 80 | .cloned() 81 | .expect("instance does not exist") 82 | }; 83 | 84 | Expr::normal(Symbol::new("System`Association"), vec![Expr::normal( 85 | Symbol::new("System`Rule"), 86 | vec![Expr::string("Value"), Expr::string(value)], 87 | )]) 88 | } 89 | 90 | fn unwrap_id_arg(arg: &Expr) -> u32 { 91 | match arg.kind() { 92 | ExprKind::Integer(int) => u32::try_from(*int).expect("id overflows u32"), 93 | _ => panic!("expected Integer instance ID argument, got: {}", arg), 94 | } 95 | } 96 | -------------------------------------------------------------------------------- /wolfram-library-link/examples/numeric_arrays.rs: -------------------------------------------------------------------------------- 1 | use wolfram_library_link::{self as wll, NumericArray, NumericArrayKind}; 2 | 3 | /// This function is loaded by evaluating: 4 | /// 5 | /// ```wolfram 6 | /// LibraryFunctionLoad[ 7 | /// "/path/to/libnumeric_arrays.dylib", 8 | /// "sum_int_numeric_array", 9 | /// {NumericArray}, 10 | /// Integer 11 | /// ] 12 | /// ``` 13 | #[wll::export] 14 | fn sum_int_numeric_array(na: &NumericArray) -> i64 { 15 | #[rustfmt::skip] 16 | let sum: i64 = match na.kind() { 17 | NumericArrayKind::Bit8(na) => na.as_slice().into_iter().copied().map(i64::from).sum(), 18 | NumericArrayKind::Bit16(na) => na.as_slice().into_iter().copied().map(i64::from).sum(), 19 | NumericArrayKind::Bit32(na) => na.as_slice().into_iter().copied().map(i64::from).sum(), 20 | NumericArrayKind::Bit64(na) => na.as_slice().into_iter().sum(), 21 | NumericArrayKind::UBit8(na) => na.as_slice().into_iter().copied().map(i64::from).sum(), 22 | NumericArrayKind::UBit16(na) => na.as_slice().into_iter().copied().map(i64::from).sum(), 23 | NumericArrayKind::UBit32(na) => na.as_slice().into_iter().copied().map(i64::from).sum(), 24 | NumericArrayKind::UBit64(na) => { 25 | match i64::try_from(na.as_slice().into_iter().sum::()) { 26 | Ok(sum) => sum, 27 | Err(_) => panic!("NumericArray UnsignedInteger64 sum overflows i64"), 28 | } 29 | }, 30 | 31 | NumericArrayKind::Real32(_) 32 | | NumericArrayKind::Real64(_) 33 | | NumericArrayKind::ComplexReal64(_) => panic!( 34 | "sum_int_numeric_array cannot handle non-integer data type: {:?}", 35 | na.data_type() 36 | ), 37 | }; 38 | 39 | sum 40 | } 41 | 42 | #[wll::export] 43 | fn sum_real_numeric_array(na: &NumericArray) -> f64 { 44 | let sum: f64 = match na.kind() { 45 | NumericArrayKind::Real32(na) => { 46 | na.as_slice().into_iter().copied().map(f64::from).sum() 47 | }, 48 | NumericArrayKind::Real64(na) => na.as_slice().into_iter().copied().sum(), 49 | _ => panic!( 50 | "sum_real_numeric_array cannot handle non-real data type: {:?}", 51 | na.data_type() 52 | ), 53 | }; 54 | 55 | sum 56 | } 57 | -------------------------------------------------------------------------------- /wolfram-library-link/examples/raw/raw_librarylink_function.rs: -------------------------------------------------------------------------------- 1 | //! This example demonstrates using the raw Rust wrappers around the LibraryLink C API to 2 | //! write a function which looks much like a classic C function using LibraryLink would. 3 | 4 | use std::mem::MaybeUninit; 5 | use std::os::raw::c_uint; 6 | 7 | use wolfram_library_link::sys::{ 8 | mint, MArgument, MNumericArray, MNumericArray_Data_Type, WolframLibraryData, 9 | LIBRARY_FUNCTION_ERROR, LIBRARY_NO_ERROR, 10 | }; 11 | 12 | /// This function is loaded by evaluating: 13 | /// 14 | /// ```wolfram 15 | /// LibraryFunctionLoad[ 16 | /// "/path/to/target/debug/examples/libraw_librarylink_function.dylib", 17 | /// "demo_function", 18 | /// {Integer, Integer}, 19 | /// Integer 20 | /// ] 21 | /// ``` 22 | #[no_mangle] 23 | pub unsafe extern "C" fn demo_function( 24 | _lib_data: WolframLibraryData, 25 | arg_count: mint, 26 | args: *mut MArgument, 27 | res: MArgument, 28 | ) -> c_uint { 29 | if arg_count != 2 { 30 | return LIBRARY_FUNCTION_ERROR; 31 | } 32 | 33 | let a: i64 = *(*args.offset(0)).integer; 34 | let b: i64 = *(*args.offset(1)).integer; 35 | 36 | *res.integer = a + b; 37 | 38 | LIBRARY_NO_ERROR 39 | } 40 | 41 | //====================================== 42 | // NumericArray's 43 | //====================================== 44 | 45 | /// This function is loaded by evaluating: 46 | /// 47 | /// ```wolfram 48 | /// LibraryFunctionLoad[ 49 | /// "/path/to/target/debug/examples/libraw_librarylink_function.dylib", 50 | /// "demo_function", 51 | /// {}, 52 | /// LibraryDataType[ByteArray] 53 | /// ] 54 | /// ``` 55 | #[no_mangle] 56 | pub unsafe extern "C" fn demo_byte_array( 57 | lib_data: WolframLibraryData, 58 | arg_count: mint, 59 | _args: *mut MArgument, 60 | res: MArgument, 61 | ) -> c_uint { 62 | const LENGTH: usize = 10; 63 | 64 | if arg_count != 0 { 65 | return LIBRARY_FUNCTION_ERROR; 66 | } 67 | 68 | let na_funs = *(*lib_data).numericarrayLibraryFunctions; 69 | 70 | // 71 | // Allocate a new MNumericArray with 10 u8 elements 72 | // 73 | 74 | let mut byte_array: MNumericArray = std::ptr::null_mut(); 75 | 76 | let err = (na_funs.MNumericArray_new.unwrap())( 77 | MNumericArray_Data_Type::MNumericArray_Type_UBit8, 78 | 1, 79 | &10, 80 | &mut byte_array, 81 | ); 82 | if err != 0 { 83 | return LIBRARY_FUNCTION_ERROR; 84 | } 85 | 86 | // 87 | // Fill the NumericArray with the number 1 to 10 88 | // 89 | 90 | let data_ptr: *mut std::ffi::c_void = 91 | (na_funs.MNumericArray_getData.unwrap())(byte_array); 92 | let data_ptr = data_ptr as *mut MaybeUninit; 93 | 94 | let slice = std::slice::from_raw_parts_mut(data_ptr, LENGTH); 95 | 96 | for (index, elem) in slice.iter_mut().enumerate() { 97 | *elem = MaybeUninit::new(index as u8) 98 | } 99 | 100 | // 101 | // Return the NumericArray 102 | // 103 | 104 | *res.numeric = byte_array; 105 | LIBRARY_NO_ERROR 106 | } 107 | -------------------------------------------------------------------------------- /wolfram-library-link/examples/raw/raw_wstp_function.rs: -------------------------------------------------------------------------------- 1 | //! This example demonstrates using the raw Rust wrappers around the LibraryLink C API to 2 | //! write a function which looks much like a classic C function using LibraryLink and 3 | //! WSTP would. 4 | //! 5 | //! This also includes an example of mixing the low-level LibraryLink bindings with the 6 | //! higher-level bindings provided by the `wstp` crate. 7 | 8 | use std::os::raw::{c_int, c_uint}; 9 | 10 | use wolfram_library_link::{ 11 | expr::{Expr, Symbol}, 12 | sys::{ 13 | self as wll_sys, WolframLibraryData, LIBRARY_FUNCTION_ERROR, LIBRARY_NO_ERROR, 14 | }, 15 | wstp::{ 16 | sys::{WSGetInteger, WSNewPacket, WSPutInteger, WSTestHead, WSLINK}, 17 | Link, 18 | }, 19 | }; 20 | 21 | /// This function is loaded by evaluating: 22 | /// 23 | /// ```wolfram 24 | /// LibraryFunctionLoad[ 25 | /// "/path/to/libraw_wstp_function.dylib", 26 | /// "demo_wstp_function", 27 | /// LinkObject, 28 | /// LinkObject 29 | /// ] 30 | /// ``` 31 | #[no_mangle] 32 | pub unsafe extern "C" fn demo_wstp_function( 33 | _lib: WolframLibraryData, 34 | link: WSLINK, 35 | ) -> c_uint { 36 | let mut i1: c_int = 0; 37 | let mut i2: c_int = 0; 38 | let mut len: c_int = 0; 39 | 40 | if WSTestHead(link, b"List\0".as_ptr() as *const i8, &mut len) == 0 { 41 | return LIBRARY_FUNCTION_ERROR; 42 | } 43 | if len != 2 { 44 | return LIBRARY_FUNCTION_ERROR; 45 | } 46 | 47 | if WSGetInteger(link, &mut i1) == 0 { 48 | return LIBRARY_FUNCTION_ERROR; 49 | } 50 | if WSGetInteger(link, &mut i2) == 0 { 51 | return LIBRARY_FUNCTION_ERROR; 52 | } 53 | if WSNewPacket(link) == 0 { 54 | return LIBRARY_FUNCTION_ERROR; 55 | } 56 | 57 | let sum = i1 + i2; 58 | 59 | if WSPutInteger(link, sum) == 0 { 60 | return LIBRARY_FUNCTION_ERROR; 61 | } 62 | 63 | return LIBRARY_NO_ERROR; 64 | } 65 | 66 | /// This example shows how the raw Rust wrappers can be mixed with higher-level wrappers 67 | /// around the Wolfram Symbolic Transfer Protocal (WSTP) for conveniently calling back 68 | /// into the Kernel to perform an evaluation. 69 | /// 70 | /// This function is loaded by evaluating: 71 | /// 72 | /// ```wolfram 73 | /// LibraryFunctionLoad[ 74 | /// "/path/to/libraw_wstp_function.dylib", 75 | /// "demo_wstp_function_callback", 76 | /// LinkObject, 77 | /// LinkObject 78 | /// ] 79 | /// ``` 80 | #[no_mangle] 81 | pub extern "C" fn demo_wstp_function_callback( 82 | lib: WolframLibraryData, 83 | mut link: WSLINK, 84 | ) -> c_uint { 85 | // Create a safe Link wrapper around the raw `WSLINK`. This is a borrowed rather than 86 | // owned Link because the caller (the Kernel) owns the link. 87 | let link: &mut Link = unsafe { Link::unchecked_ref_cast_mut(&mut link) }; 88 | 89 | // Skip reading the argument list packet. 90 | if link.raw_get_next().and_then(|_| link.new_packet()).is_err() { 91 | return LIBRARY_FUNCTION_ERROR; 92 | } 93 | 94 | let callback_link = unsafe { (*lib).getWSLINK.unwrap()(lib) }; 95 | let mut callback_link = callback_link as wstp::sys::WSLINK; 96 | 97 | { 98 | let safe_callback_link = 99 | unsafe { Link::unchecked_ref_cast_mut(&mut callback_link) }; 100 | 101 | safe_callback_link 102 | // EvaluatePacket[Print["Hello, World! --- WSTP"]] 103 | .put_expr(&Expr::normal(Symbol::new("System`EvaluatePacket"), vec![ 104 | Expr::normal(Symbol::new("System`Print"), vec![Expr::string( 105 | "Hello, World! --- WSTP", 106 | )]), 107 | ])) 108 | .unwrap(); 109 | 110 | unsafe { 111 | (*lib).processWSLINK.unwrap()( 112 | safe_callback_link.raw_link() as wll_sys::WSLINK 113 | ); 114 | } 115 | 116 | // Skip the return value packet. This is necessary, otherwise the link has 117 | // unread data and the return value of this function cannot be processed properly. 118 | if safe_callback_link 119 | .raw_get_next() 120 | .and_then(|_| safe_callback_link.new_packet()) 121 | .is_err() 122 | { 123 | return LIBRARY_FUNCTION_ERROR; 124 | } 125 | } 126 | 127 | link.put_expr(&Expr::string("returned normally")).unwrap(); 128 | 129 | return LIBRARY_NO_ERROR; 130 | } 131 | 132 | /// This example makes use of the [`wstp`][wstp] crate to provide a safe wrapper around 133 | /// around the WSTP link object, which can be used to read the argument expression and 134 | /// write out the return expression. 135 | /// 136 | /// ```wolfram 137 | /// function = LibraryFunctionLoad[ 138 | /// "raw_wstp_function", 139 | /// "wstp_expr_function", 140 | /// LinkObject, 141 | /// LinkObject 142 | /// ]; 143 | /// ``` 144 | #[no_mangle] 145 | pub extern "C" fn wstp_expr_function( 146 | _lib: WolframLibraryData, 147 | mut unsafe_link: WSLINK, 148 | ) -> c_uint { 149 | let link: &mut Link = unsafe { Link::unchecked_ref_cast_mut(&mut unsafe_link) }; 150 | 151 | let expr = match link.get_expr() { 152 | Ok(expr) => expr, 153 | Err(err) => { 154 | // Skip reading the argument list packet. 155 | if link.raw_get_next().and_then(|_| link.new_packet()).is_err() { 156 | return LIBRARY_FUNCTION_ERROR; 157 | } 158 | 159 | let err = Expr::string(err.to_string()); 160 | let err = Expr::normal(Symbol::new("System`Failure"), vec![ 161 | Expr::string("WSTP Error"), 162 | Expr::normal(Symbol::new("System`Association"), vec![Expr::normal( 163 | Symbol::new("System`Rule"), 164 | vec![Expr::string("Message"), err], 165 | )]), 166 | ]); 167 | match link.put_expr(&err) { 168 | Ok(()) => return LIBRARY_NO_ERROR, 169 | Err(_) => return LIBRARY_FUNCTION_ERROR, 170 | } 171 | }, 172 | }; 173 | 174 | let expr_string = format!("Input: {}", expr.to_string()); 175 | 176 | match link.put_expr(&Expr::string(expr_string)) { 177 | Ok(()) => LIBRARY_NO_ERROR, 178 | Err(_) => LIBRARY_FUNCTION_ERROR, 179 | } 180 | } 181 | -------------------------------------------------------------------------------- /wolfram-library-link/examples/tests/README.md: -------------------------------------------------------------------------------- 1 | # wolfram-library-link/examples/tests/ 2 | 3 | This directory contains tests for wolfram-library-link functionality, which are declared 4 | as cargo examples so that they produce dynamic libraries that can be loaded by 5 | standard Wolfram Language MUnit tests. 6 | 7 | This is necessary because much of wolfram-library-link depends on being dynamically linked 8 | into a running Wolfram Language Kernel, so tests for that functionality must necessarily 9 | be initiated by the Kernel. Writing these as cargo integration tests run using the 10 | standard `cargo test` command would fail because there would be no Wolfram Kernel to load 11 | and call the LibraryLink functions being tested. 12 | -------------------------------------------------------------------------------- /wolfram-library-link/examples/tests/main.rs: -------------------------------------------------------------------------------- 1 | mod test_native_args; 2 | mod test_share_counts; 3 | mod test_threading; 4 | 5 | mod test_data_store; 6 | mod test_images; 7 | mod test_numeric_array_conversions; 8 | mod test_wstp; 9 | -------------------------------------------------------------------------------- /wolfram-library-link/examples/tests/test_data_store.rs: -------------------------------------------------------------------------------- 1 | use std::os::raw::c_int; 2 | use wolfram_library_link::{ 3 | self as wll, 4 | sys::{self, WolframLibraryData}, 5 | DataStore, NumericArray, 6 | }; 7 | 8 | 9 | #[no_mangle] 10 | pub unsafe extern "C" fn WolframLibrary_initialize(lib: WolframLibraryData) -> c_int { 11 | match wll::initialize(lib) { 12 | Ok(()) => return 0, 13 | Err(()) => return 1, 14 | } 15 | } 16 | 17 | #[wll::export] 18 | fn test_empty_data_store() -> DataStore { 19 | DataStore::new() 20 | } 21 | 22 | #[wll::export] 23 | fn test_single_int_data_store() -> DataStore { 24 | let mut data = DataStore::new(); 25 | data.add_i64(1); 26 | 27 | data 28 | } 29 | 30 | #[wll::export] 31 | fn test_multiple_int_data_store() -> DataStore { 32 | let mut data = DataStore::new(); 33 | data.add_i64(1); 34 | data.add_i64(2); 35 | data.add_i64(3); 36 | 37 | data 38 | } 39 | 40 | #[wll::export] 41 | fn test_unnamed_heterogenous_data_store() -> DataStore { 42 | let mut data = DataStore::new(); 43 | data.add_i64(1); 44 | data.add_f64(2.0); 45 | data.add_str("hello"); 46 | 47 | data 48 | } 49 | 50 | #[wll::export] 51 | fn test_named_heterogenous_data_store() -> DataStore { 52 | let mut data = DataStore::new(); 53 | data.add_named_i64("an i64", 1); 54 | data.add_named_f64("an f64", 2.0); 55 | data.add_named_str("a str", "hello"); 56 | 57 | data 58 | } 59 | 60 | #[wll::export] 61 | fn test_named_and_unnamed_heterogenous_data_store() -> DataStore { 62 | let mut data = DataStore::new(); 63 | data.add_i64(1); 64 | data.add_named_f64("real", 2.0); 65 | data.add_named_str("hello", "world"); 66 | 67 | data 68 | } 69 | 70 | //====================================== 71 | // Non-atomic types 72 | //====================================== 73 | 74 | #[wll::export] 75 | fn test_named_numeric_array_data_store() -> DataStore { 76 | let array = NumericArray::::from_slice(&[1, 2, 3]).into_generic(); 77 | 78 | let mut data = DataStore::new(); 79 | data.add_named_numeric_array("array", array); 80 | 81 | data 82 | } 83 | 84 | #[wll::export] 85 | fn test_nested_data_store() -> DataStore { 86 | let mut inner = DataStore::new(); 87 | inner.add_named_bool("is_inner", true); 88 | 89 | let mut outer = DataStore::new(); 90 | outer.add_named_bool("is_inner", false); 91 | outer.add_data_store(inner); 92 | 93 | outer 94 | } 95 | 96 | #[wll::export] 97 | fn test_iterated_nested_data_store() -> DataStore { 98 | let mut store = DataStore::new(); 99 | 100 | for level in 0..3 { 101 | store.add_named_i64("level", level); 102 | let mut new = DataStore::new(); 103 | new.add_data_store(store); 104 | store = new; 105 | } 106 | 107 | store 108 | } 109 | 110 | //====================================== 111 | // DataStore arguments 112 | //====================================== 113 | 114 | #[wll::export] 115 | fn test_data_store_arg(ds: DataStore) -> i64 { 116 | ds.len() as i64 117 | } 118 | 119 | //====================================== 120 | // DataStore nodes 121 | //====================================== 122 | 123 | #[wll::export] 124 | fn test_data_store_nodes() { 125 | { 126 | let mut data = DataStore::new(); 127 | data.add_i64(5); 128 | 129 | assert_eq!(data.len(), 1); 130 | 131 | let node = data.first_node().expect("got first node"); 132 | 133 | let ty: sys::type_t = node.data_type_raw(); 134 | 135 | assert_eq!(ty, sys::MType_Integer as i32); 136 | } 137 | 138 | // Test DataStoreNode::name() method. 139 | { 140 | let mut data = DataStore::new(); 141 | data.add_named_i64("hello", 5); 142 | 143 | assert_eq!(data.len(), 1); 144 | 145 | let node = data.first_node().expect("got first node"); 146 | 147 | assert_eq!(node.data_type_raw(), sys::MType_Integer as i32); 148 | assert_eq!(node.name(), Some("hello".to_owned())) 149 | } 150 | 151 | // Test DataStore::nodes() method and Debug formatting of DataStoreNode. 152 | { 153 | let mut store = DataStore::new(); 154 | 155 | store.add_i64(5); 156 | store.add_named_bool("condition", true); 157 | store.add_str("Hello, World!"); 158 | 159 | let mut nodes = store.nodes(); 160 | 161 | assert_eq!( 162 | format!("{:?}", nodes.next().unwrap()), 163 | r#"DataStoreNode { name: None, value: 5 }"# 164 | ); 165 | assert_eq!( 166 | format!("{:?}", nodes.next().unwrap()), 167 | r#"DataStoreNode { name: Some("condition"), value: true }"# 168 | ); 169 | assert_eq!( 170 | format!("{:?}", nodes.next().unwrap()), 171 | r#"DataStoreNode { name: None, value: "Hello, World!" }"# 172 | ); 173 | assert!(nodes.next().is_none()); 174 | } 175 | } 176 | -------------------------------------------------------------------------------- /wolfram-library-link/examples/tests/test_images.rs: -------------------------------------------------------------------------------- 1 | use wolfram_library_link::{ 2 | self as wll, ColorSpace, Image, NumericArray, Pixel, UninitImage, UninitNumericArray, 3 | }; 4 | 5 | 6 | #[wll::export] 7 | fn test_image_arg(image: &Image) -> NumericArray { 8 | let mut array = UninitNumericArray::from_dimensions(&[image.flattened_length()]); 9 | 10 | for pair in image 11 | .as_slice() 12 | .iter() 13 | .zip(array.as_slice_mut().into_iter()) 14 | { 15 | let (pixel, elem): (&i8, &mut std::mem::MaybeUninit) = pair; 16 | elem.write(*pixel); 17 | } 18 | 19 | // Safety: We iterated over every element in `array` and initialized it with the 20 | // corresponding pixel value from `image`. 21 | unsafe { array.assume_init() } 22 | } 23 | 24 | // fn test_image_manual_arg(image: Image) -> NumericArray { 25 | // todo!("") 26 | // } 27 | 28 | #[wll::export] 29 | fn test_create_bitmap_image() -> Image { 30 | let width = 2; 31 | let height = 2; 32 | let channels = 1; 33 | 34 | let mut image = UninitImage::::new_2d( 35 | width, 36 | height, 37 | channels, 38 | ColorSpace::Automatic, 39 | false, 40 | ); 41 | 42 | image.set(Pixel::D2([1, 1]), 1, false); 43 | image.set(Pixel::D2([1, 2]), 1, true); 44 | image.set(Pixel::D2([2, 1]), 1, true); 45 | image.set(Pixel::D2([2, 2]), 1, false); 46 | 47 | unsafe { image.assume_init() } 48 | } 49 | 50 | /// Create an image with four pixels, where the top left image is red, the top right 51 | /// pixel is green, the bottom left pixel is blue, and the bottom right pixel is light 52 | /// gray. 53 | #[wll::export] 54 | fn test_create_color_rgb_u8_image() -> Image { 55 | let width = 2; 56 | let height = 2; 57 | let channels = 3; // Red, green, and blue. 58 | 59 | let mut image: UninitImage = 60 | UninitImage::new_2d(width, height, channels, ColorSpace::RGB, false); 61 | 62 | // Red, green, and blue channels indices. 63 | const R: usize = 1; 64 | const G: usize = 2; 65 | const B: usize = 3; 66 | 67 | // Set every pixel value to black. The image data is otherwise completely 68 | // uninitialized memory, and can contain arbitrary values. 69 | image.zero(); 70 | 71 | // Set the top left, top right, and bottom left pixels on only one color channel. 72 | image.set(Pixel::D2([1, 1]), R, u8::MAX); 73 | image.set(Pixel::D2([1, 2]), G, u8::MAX); 74 | image.set(Pixel::D2([2, 1]), B, u8::MAX); 75 | 76 | // Make this pixel white, by setting R, G, and B channels to ~80%. 77 | image.set(Pixel::D2([2, 2]), R, 200); 78 | image.set(Pixel::D2([2, 2]), G, 200); 79 | image.set(Pixel::D2([2, 2]), B, 200); 80 | 81 | unsafe { image.assume_init() } 82 | } 83 | 84 | /// Create an image with four pixels, where the top left image is red, the top right 85 | /// pixel is green, the bottom left pixel is blue, and the bottom right pixel is light 86 | /// gray. 87 | #[wll::export] 88 | fn test_create_color_rgb_f32_image() -> Image { 89 | let width = 2; 90 | let height = 2; 91 | let channels = 3; // Red, green, and blue. 92 | 93 | let mut image: UninitImage = 94 | UninitImage::new_2d(width, height, channels, ColorSpace::RGB, false); 95 | 96 | // Red, green, and blue channels indices. 97 | const R: usize = 1; 98 | const G: usize = 2; 99 | const B: usize = 3; 100 | 101 | // Set every pixel value to black. The image data is otherwise completely 102 | // uninitialized memory, and can contain arbitrary values. 103 | image.zero(); 104 | 105 | // Set the top left, top right, and bottom left pixels on only one color channel. 106 | image.set(Pixel::D2([1, 1]), R, 1.0); 107 | image.set(Pixel::D2([1, 2]), G, 1.0); 108 | image.set(Pixel::D2([2, 1]), B, 1.0); 109 | 110 | // Make this pixel white, by setting R, G, and B channels to 80%. 111 | image.set(Pixel::D2([2, 2]), R, 0.8); 112 | image.set(Pixel::D2([2, 2]), G, 0.8); 113 | image.set(Pixel::D2([2, 2]), B, 0.8); 114 | 115 | unsafe { image.assume_init() } 116 | } 117 | -------------------------------------------------------------------------------- /wolfram-library-link/examples/tests/test_native_args.rs: -------------------------------------------------------------------------------- 1 | use std::ffi::CString; 2 | 3 | use wolfram_library_link::{ 4 | self as wll, 5 | sys::{mint, mreal}, 6 | NumericArray, UninitNumericArray, 7 | }; 8 | 9 | //====================================== 10 | // Primitive data types 11 | //====================================== 12 | 13 | #[wll::export] 14 | fn test_no_args() -> i64 { 15 | 4 16 | } 17 | 18 | #[wll::export] 19 | fn test_ret_void() { 20 | // Do nothing. 21 | } 22 | 23 | //------------ 24 | // mint, mreal 25 | //------------ 26 | 27 | #[wll::export] 28 | fn test_mint(x: mint) -> mint { 29 | x * x 30 | } 31 | 32 | // Test NativeFunction impl for raw function using raw MArguments. 33 | #[wll::export] 34 | fn test_raw_mint(args: &[wll::sys::MArgument], ret: wll::sys::MArgument) { 35 | if args.len() != 1 { 36 | panic!("unexpected number of arguments"); 37 | } 38 | 39 | let x: mint = unsafe { *args[0].integer }; 40 | 41 | unsafe { 42 | *ret.integer = x * x; 43 | } 44 | } 45 | 46 | #[wll::export] 47 | fn test_mint_mint(x: mint, y: mint) -> mint { 48 | x + y 49 | } 50 | 51 | #[wll::export] 52 | fn test_mreal(x: mreal) -> mreal { 53 | x * x 54 | } 55 | 56 | //------------ 57 | // i64, f64 58 | //------------ 59 | 60 | #[wll::export] 61 | fn test_i64(x: i64) -> i64 { 62 | x * x 63 | } 64 | 65 | #[wll::export] 66 | fn test_i64_i64(x: i64, y: i64) -> i64 { 67 | x + y 68 | } 69 | 70 | #[wll::export] 71 | fn test_f64(x: f64) -> f64 { 72 | x * x 73 | } 74 | 75 | //-------- 76 | // Strings 77 | //-------- 78 | 79 | // fn test_str(string: &str) -> String { 80 | // string.chars().rev().collect() 81 | // } 82 | 83 | #[wll::export] 84 | fn test_string(string: String) -> String { 85 | string.chars().rev().collect() 86 | } 87 | 88 | #[wll::export] 89 | fn test_c_string(string: CString) -> i64 { 90 | i64::try_from(string.as_bytes().len()).expect("string len usize overflows i64") 91 | } 92 | 93 | //------- 94 | // Panics 95 | //------- 96 | 97 | #[wll::export] 98 | fn test_panic() { 99 | panic!("this function panicked"); 100 | } 101 | 102 | //====================================== 103 | // NumericArray's 104 | //====================================== 105 | 106 | #[wll::export] 107 | fn total_i64(list: &NumericArray) -> i64 { 108 | list.as_slice().into_iter().sum() 109 | } 110 | 111 | /// Get the sign of every element in `list` as a numeric array of 0's and 1's. 112 | /// 113 | /// The returned array will have the same dimensions as `list`. 114 | #[wll::export] 115 | fn positive_i64(list: &NumericArray) -> NumericArray { 116 | let mut bools: UninitNumericArray = 117 | UninitNumericArray::from_dimensions(list.dimensions()); 118 | 119 | for pair in list.as_slice().into_iter().zip(bools.as_slice_mut()) { 120 | let (elem, entry): (&i64, &mut std::mem::MaybeUninit) = pair; 121 | 122 | entry.write(u8::from(elem.is_positive())); 123 | } 124 | 125 | unsafe { bools.assume_init() } 126 | } 127 | -------------------------------------------------------------------------------- /wolfram-library-link/examples/tests/test_numeric_array_conversions.rs: -------------------------------------------------------------------------------- 1 | use wolfram_library_link::{ 2 | self as wll, NumericArray, NumericArrayConvertMethod as Method, 3 | }; 4 | 5 | fn from_slice(slice: &[T]) -> NumericArray { 6 | NumericArray::from_slice(slice) 7 | } 8 | 9 | #[wll::export] 10 | fn test_na_conversions() { 11 | // 12 | // i16 -> i8 conversions 13 | // 14 | 15 | assert!(from_slice(&[i16::MAX]) 16 | .convert_to::(Method::Check, 1.0) 17 | .is_err()); 18 | 19 | assert!(from_slice(&[i16::MAX]) 20 | .convert_to::(Method::Cast, 1.0) 21 | .is_err()); 22 | 23 | assert!(from_slice(&[i16::MAX]) 24 | .convert_to::(Method::Coerce, 1.0) 25 | .is_err()); 26 | 27 | assert!(from_slice(&[i16::MAX]) 28 | .convert_to::(Method::Round, 1.0) 29 | .is_err()); 30 | 31 | assert_eq!( 32 | from_slice(&[i16::MAX]) 33 | .convert_to::(Method::Scale, 1.0) 34 | .unwrap() 35 | .as_slice(), 36 | [i8::MAX] 37 | ); 38 | } 39 | -------------------------------------------------------------------------------- /wolfram-library-link/examples/tests/test_share_counts.rs: -------------------------------------------------------------------------------- 1 | use wolfram_library_link::{self as wll, DataStore, NumericArray}; 2 | 3 | 4 | #[wll::export] 5 | fn test_na_automatic_count(array: &NumericArray) -> i64 { 6 | array.share_count() as i64 7 | } 8 | 9 | #[wll::export] 10 | fn test_na_constant_count(array: &NumericArray) -> i64 { 11 | array.share_count() as i64 12 | } 13 | 14 | #[wll::export] 15 | fn test_na_manual_count(array: NumericArray) -> i64 { 16 | array.share_count() as i64 17 | } 18 | 19 | #[wll::export] 20 | fn test_na_shared_count(array: NumericArray) -> i64 { 21 | array.share_count() as i64 22 | } 23 | 24 | // 25 | 26 | #[wll::export] 27 | fn test_na_constant_are_ptr_eq( 28 | array1: &NumericArray, 29 | array2: &NumericArray, 30 | ) -> DataStore { 31 | let mut data = DataStore::new(); 32 | data.add_bool(array1.ptr_eq(&array2)); 33 | data.add_i64(array1.share_count() as i64); 34 | data 35 | } 36 | 37 | #[wll::export] 38 | fn test_na_manual_are_not_ptr_eq( 39 | mut array1: NumericArray, 40 | array2: NumericArray, 41 | ) -> DataStore { 42 | let mut data = DataStore::new(); 43 | data.add_bool(array1.ptr_eq(&array2)); 44 | data.add_i64(array1.share_count() as i64); 45 | data.add_bool(array1.as_slice_mut().is_some()); 46 | data 47 | } 48 | 49 | #[wll::export] 50 | fn test_na_shared_are_ptr_eq( 51 | mut array1: NumericArray, 52 | array2: NumericArray, 53 | ) -> DataStore { 54 | let mut data = DataStore::new(); 55 | data.add_bool(array1.ptr_eq(&array2)); 56 | data.add_i64(array1.share_count() as i64); 57 | data.add_bool(array1.as_slice_mut().is_some()); 58 | data 59 | } 60 | 61 | //---------------------------- 62 | // Test cloning NumericArray's 63 | //---------------------------- 64 | 65 | #[wll::export] 66 | fn test_na_clone() -> bool { 67 | let array = NumericArray::::from_slice(&[1, 2, 3]); 68 | 69 | assert!(array.share_count() == 0); 70 | 71 | let clone = array.clone(); 72 | 73 | assert!(!array.ptr_eq(&clone)); 74 | 75 | assert!(array.share_count() == 0); 76 | assert!(clone.share_count() == 0); 77 | 78 | true 79 | } 80 | 81 | #[wll::export] 82 | fn test_na_shared_clone(array: NumericArray) -> bool { 83 | assert!(array.share_count() == 1); 84 | 85 | let clone = array.clone(); 86 | 87 | assert!(!array.ptr_eq(&clone)); 88 | 89 | assert!(array.share_count() == 1); 90 | assert!(clone.share_count() == 0); 91 | 92 | true 93 | } 94 | -------------------------------------------------------------------------------- /wolfram-library-link/examples/tests/test_threading.rs: -------------------------------------------------------------------------------- 1 | use std::panic; 2 | 3 | use wolfram_library_link::{ 4 | self as wll, 5 | expr::{Expr, Symbol}, 6 | }; 7 | 8 | #[wll::export] 9 | fn test_runtime_function_from_main_thread() -> bool { 10 | let expr = Expr::normal(Symbol::new("System`Plus"), vec![ 11 | Expr::from(2), 12 | Expr::from(2), 13 | ]); 14 | 15 | wll::evaluate(&expr) == Expr::from(4) 16 | } 17 | 18 | #[wll::export] 19 | fn test_runtime_function_from_non_main_thread() -> String { 20 | let child = std::thread::spawn(|| { 21 | panic::set_hook(Box::new(|_| { 22 | // Do nothing, just to avoid printing panic message to stderr. 23 | })); 24 | 25 | let result = panic::catch_unwind(|| { 26 | wll::evaluate(&Expr::normal(Symbol::new("System`Plus"), vec![ 27 | Expr::from(2), 28 | Expr::from(2), 29 | ])) 30 | }); 31 | 32 | // Restore the previous (default) hook. 33 | let _ = panic::take_hook(); 34 | 35 | result 36 | }); 37 | 38 | let result = child.join().unwrap(); 39 | 40 | match result { 41 | Ok(_) => "didn't panic".to_owned(), 42 | // We expect the thread to panic 43 | Err(panic) => { 44 | if let Some(str) = panic.downcast_ref::<&str>() { 45 | format!("PANIC: {}", str) 46 | } else if let Some(string) = panic.downcast_ref::() { 47 | format!("PANIC: {}", string) 48 | } else { 49 | "PANIC".to_owned() 50 | } 51 | }, 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /wolfram-library-link/examples/tests/test_wstp.rs: -------------------------------------------------------------------------------- 1 | use wolfram_library_link::{ 2 | export, 3 | expr::Expr, 4 | wstp::{self, Link}, 5 | }; 6 | 7 | #[export(wstp)] 8 | fn test_wstp_fn_empty(_link: &mut Link) { 9 | // Do nothing. 10 | } 11 | 12 | #[export(wstp)] 13 | fn test_wstp_fn_panic_immediately(_link: &mut Link) { 14 | panic!("successful panic") 15 | } 16 | 17 | #[export(wstp)] 18 | fn test_wstp_fn_panic_immediately_with_formatting(_link: &mut Link) { 19 | panic!("successful {} panic", "formatted") 20 | } 21 | 22 | /// Test that the wrapper function generated by `#[export(wstp)]` will correctly handle 23 | /// panicking when `link` has been left in a `!link.is_ready()` state. 24 | #[export(wstp)] 25 | fn test_wstp_panic_with_empty_link(link: &mut Link) { 26 | link.raw_get_next().unwrap(); 27 | link.new_packet().unwrap(); 28 | 29 | assert!(!link.is_ready()); 30 | assert!(link.error().is_none()); 31 | 32 | // Panic while there is no content on `link` to be cleared by the panic handler. 33 | // This essentially tests that the panic handler checks `if link.is_ready() { ... }` 34 | // before trying to clear content off of the link. 35 | panic!("panic while !link.is_ready()"); 36 | } 37 | 38 | #[export(wstp)] 39 | fn test_wstp_fn_panic_partial_result(link: &mut Link) { 40 | link.raw_get_next().unwrap(); 41 | link.new_packet().unwrap(); 42 | 43 | assert!(!link.is_ready()); 44 | assert!(link.error().is_none()); 45 | 46 | 47 | // Indicate that we will be writing a 3 element list, but only write one 48 | // of the elements. 49 | link.put_function("System`List", 3).unwrap(); 50 | link.put_i64(1).unwrap(); 51 | 52 | // Now panic with the link containing partial data. 53 | panic!("incomplete result") 54 | } 55 | 56 | #[export(wstp)] 57 | fn test_wstp_fn_return_partial_result(link: &mut Link) { 58 | link.raw_get_next().unwrap(); 59 | link.new_packet().unwrap(); 60 | 61 | assert!(!link.is_ready()); 62 | assert!(link.error().is_none()); 63 | 64 | link.put_function("System`List", 3).unwrap(); 65 | 66 | // Return without completing the List expression 67 | } 68 | 69 | /// Test that the wrapper function generated by `#[export(wstp)]` will check for and clear 70 | /// any link errors that might have occurred within the user code. 71 | #[export(wstp)] 72 | fn test_wstp_fn_poison_link_and_panic(link: &mut Link) { 73 | // Cause a link failure by trying to Get the wrong head. 74 | assert!(link.test_head("NotTheRightHead").is_err()); 75 | 76 | // Assert that the link now has an uncleared error. 77 | assert_eq!(link.error().unwrap().code(), Some(wstp::sys::WSEGSEQ)); 78 | 79 | // Verify that trying to do an operation on the link returns the same error as 80 | // `link.error()`. 81 | assert_eq!( 82 | link.put_str("some result").unwrap_err().code(), 83 | Some(wstp::sys::WSEGSEQ) 84 | ); 85 | 86 | // Panic while leaving the link in the error state. 87 | panic!("successful panic") 88 | } 89 | 90 | #[export(wstp)] 91 | fn test_wstp_expr_return_null(_args: Vec) { 92 | // Do nothing. 93 | } 94 | -------------------------------------------------------------------------------- /wolfram-library-link/examples/wstp.rs: -------------------------------------------------------------------------------- 1 | //! This example demonstrates how WSTP links can be used in LibraryLink functions to pass 2 | //! arbitrary expressions as the function arguments and return value. 3 | 4 | use wolfram_library_link::{ 5 | self as wll, 6 | expr::{Expr, ExprKind, Number, Symbol}, 7 | wstp::Link, 8 | }; 9 | 10 | // Generates a special "loader" function, which returns an Association containing the 11 | // loaded forms of all functions exported by this library. 12 | // 13 | // The loader can be loaded and used by evaluating: 14 | // 15 | // ``` 16 | // loadFunctions = LibraryFunctionLoad[ 17 | // "libwstp_example", 18 | // "load_wstp_functions", 19 | // LinkObject, 20 | // LinkObject 21 | // ]; 22 | // 23 | // $functions = loadFunctions["libwstp_example"]; 24 | // ``` 25 | wll::generate_loader!(load_wstp_functions); 26 | 27 | //====================================== 28 | // Using `&mut Link` 29 | //====================================== 30 | 31 | //------------------ 32 | // square_wstp() 33 | //------------------ 34 | 35 | /// Define a WSTP function that squares a number. 36 | /// 37 | /// ```wolfram 38 | /// square = $functions["square_wstp"]; 39 | /// 40 | /// square[4] (* Returns 16 *) 41 | /// ``` 42 | #[wll::export(wstp)] 43 | fn square_wstp(link: &mut Link) { 44 | // Get the number of elements in the arguments list. 45 | let arg_count: usize = link.test_head("System`List").unwrap(); 46 | 47 | if arg_count != 1 { 48 | panic!("square_wstp: expected to get a single argument"); 49 | } 50 | 51 | // Get the argument value. 52 | let x = link.get_i64().expect("expected Integer argument"); 53 | 54 | // Write the return value. 55 | link.put_i64(x * x).unwrap(); 56 | } 57 | 58 | //------------------ 59 | // count_args() 60 | //------------------ 61 | 62 | /// Define a function that returns an integer count of the number of arguments it was 63 | /// given. 64 | /// 65 | /// The exported LibraryLink function can be loaded and used by evaluating: 66 | /// 67 | /// ```wolfram 68 | /// countArgs = $functions["count_args"]; 69 | /// 70 | /// countArgs[a] (* Returns 1) 71 | /// countArgs[a, b, c] (* Returns 3 *) 72 | /// ``` 73 | #[wll::export(wstp)] 74 | fn count_args(link: &mut Link) { 75 | // Get the number of elements in the arguments list. 76 | let arg_count: usize = link.test_head("System`List").unwrap(); 77 | 78 | // Discard the remaining argument data. 79 | link.new_packet().unwrap(); 80 | 81 | // Write the return value. 82 | link.put_i64(i64::try_from(arg_count).unwrap()).unwrap(); 83 | } 84 | 85 | //------------------ 86 | // total_args_i64() 87 | //------------------ 88 | 89 | /// Define a function that returns the sum of it's integer arguments. 90 | /// 91 | /// The exported LibraryLink function can be loaded and used by evaluating: 92 | /// 93 | /// ```wolfram 94 | /// totalArgsI64 = $functions["total_args_i64"]; 95 | /// 96 | /// totalArgsI64[1, 1, 2, 3, 5] (* Returns 12 *) 97 | /// ``` 98 | #[wll::export(wstp)] 99 | fn total_args_i64(link: &mut Link) { 100 | // Check that we recieved a functions arguments list, and get the number of arguments. 101 | let arg_count: usize = link.test_head("System`List").unwrap(); 102 | 103 | let mut total: i64 = 0; 104 | 105 | // Get each argument, assuming that they are all integers, and add it to the total. 106 | for _ in 0..arg_count { 107 | let term = link.get_i64().expect("expected Integer argument"); 108 | total += term; 109 | } 110 | 111 | // Write the return value to the link. 112 | link.put_i64(total).unwrap(); 113 | } 114 | 115 | //------------------ 116 | // string_join() 117 | //------------------ 118 | 119 | /// Define a function that will join its string arguments into a single string. 120 | /// 121 | /// The exported LibraryLink function can be loaded and used by evaluating: 122 | /// 123 | /// ```wolfram 124 | /// stringJoin = $functions["string_join"]; 125 | /// 126 | /// stringJoin["Foo", "Bar"] (* Returns "FooBar" *) 127 | /// stringJoin["Foo", "Bar", "Baz"] (* Returns "FooBarBaz" *) 128 | /// stringJoin[] (* Returns "" *) 129 | /// ``` 130 | #[wll::export(wstp)] 131 | fn string_join(link: &mut Link) { 132 | use wstp::LinkStr; 133 | 134 | let arg_count = link.test_head("System`List").unwrap(); 135 | 136 | let mut buffer = String::new(); 137 | 138 | for _ in 0..arg_count { 139 | let elem: LinkStr<'_> = link.get_string_ref().expect("expected String argument"); 140 | buffer.push_str(elem.as_str()); 141 | } 142 | 143 | // Write the joined string value to the link. 144 | link.put_str(buffer.as_str()).unwrap(); 145 | } 146 | 147 | //------------------ 148 | // link_expr_identity() 149 | //------------------ 150 | 151 | /// Define a function that returns the argument expression that was sent over the link. 152 | /// That expression will be a list of the arguments passed to this LibraryFunction[..]. 153 | /// 154 | /// ```wolfram 155 | /// linkExprIdentity = $functions["link_expr_identity"]; 156 | /// 157 | /// linkExprIdentity[5] (* Returns {5} *) 158 | /// linkExprIdentity[a, b] (* Returns {a, b} *) 159 | /// ``` 160 | #[wll::export(wstp)] 161 | fn link_expr_identity(link: &mut Link) { 162 | let expr = link.get_expr().unwrap(); 163 | assert!(!link.is_ready()); 164 | link.put_expr(&expr).unwrap(); 165 | } 166 | 167 | //------------------ 168 | // expr_string_join() 169 | //------------------ 170 | 171 | /// This example is an alternative to the `string_join()` example. 172 | /// 173 | /// This example shows using the `Expr` and `ExprKind` types to process expressions on 174 | /// the WSTP link. 175 | #[wll::export(wstp)] 176 | fn expr_string_join(link: &mut Link) { 177 | let expr = link.get_expr().unwrap(); 178 | 179 | let list = expr.try_as_normal().unwrap(); 180 | assert!(list.has_head(&Symbol::new("System`List"))); 181 | 182 | let mut buffer = String::new(); 183 | for elem in list.elements() { 184 | match elem.kind() { 185 | ExprKind::String(str) => buffer.push_str(str), 186 | _ => panic!("expected String argument, got: {:?}", elem), 187 | } 188 | } 189 | 190 | link.put_str(buffer.as_str()).unwrap() 191 | } 192 | 193 | //====================================== 194 | // Using `Vec` argument list 195 | //====================================== 196 | 197 | //------------------ 198 | // total() 199 | //------------------ 200 | 201 | #[wll::export(wstp)] 202 | fn total(args: Vec) -> Expr { 203 | let mut total = Number::Integer(0); 204 | 205 | for (index, arg) in args.into_iter().enumerate() { 206 | let number = match arg.try_as_number() { 207 | Some(number) => number, 208 | None => panic!( 209 | "expected argument at position {} to be a number, got {}", 210 | // Add +1 to display using WL 1-based indexing. 211 | index + 1, 212 | arg 213 | ), 214 | }; 215 | 216 | use Number::{Integer, Real}; 217 | 218 | total = match (total, number) { 219 | // If the sum and new term are integers, use integers. 220 | (Integer(total), Integer(term)) => Integer(total + term), 221 | // Otherwise, if the either the total or new term are machine real numbers, 222 | // use floating point numbers. 223 | (Integer(int), Real(real)) | (Real(real), Integer(int)) => { 224 | Number::real(int as f64 + *real) 225 | }, 226 | (Real(total), Real(term)) => Real(total + term), 227 | } 228 | } 229 | 230 | Expr::number(total) 231 | } 232 | -------------------------------------------------------------------------------- /wolfram-library-link/src/async_tasks.rs: -------------------------------------------------------------------------------- 1 | //! Support for Wolfram Language asynchronous tasks. 2 | //! 3 | //! # Credits 4 | //! 5 | //! The implementations of this module and the associated examples are based on the path 6 | //! laid out by [this StackOverflow answer](https://mathematica.stackexchange.com/a/138433). 7 | 8 | use std::{ 9 | ffi::{c_void, CString}, 10 | panic, 11 | }; 12 | 13 | use static_assertions::assert_not_impl_any; 14 | 15 | use crate::{rtl, sys, DataStore}; 16 | 17 | 18 | /// Handle to a Wolfram Language [`AsynchronousTaskObject`][ref/AsynchronousTaskObject]WL 19 | /// instance. 20 | /// 21 | /// Use [`spawn_with_thread()`][AsyncTaskObject::spawn_with_thread] to spawn a new 22 | /// asynchronous task. 23 | /// 24 | /// [ref/AsynchronousTaskObject]: https://reference.wolfram.com/language/ref/AsynchronousTaskObject.html 25 | #[derive(Debug)] 26 | pub struct AsyncTaskObject(sys::mint); 27 | 28 | // TODO: Determine if it would be safe for this type to implement Copy/Clone. 29 | assert_not_impl_any!(AsyncTaskObject: Copy, Clone); 30 | 31 | 32 | //====================================== 33 | // Impls 34 | //====================================== 35 | 36 | impl AsyncTaskObject { 37 | /// Spawn a new Wolfram Language asynchronous task. 38 | /// 39 | /// This method can be used within a LibraryLink function that was called via 40 | /// 41 | /// ```wolfram 42 | /// Internal`CreateAsynchronousTask[ 43 | /// _LibraryFunction, 44 | /// args_List, 45 | /// handler 46 | /// ] 47 | /// ``` 48 | /// 49 | /// to create a new [`AsynchronousTaskObject`][ref/AsynchronousTaskObject]WL 50 | /// that uses a background thread that can generate events that will be processed 51 | /// asynchronously by the Wolfram Language. 52 | /// 53 | /// The background thread is given an `AsyncTaskObject` that has the same id as 54 | /// the `AsyncTaskObject` returned from this function. Events generated by the 55 | /// background thread using [`raise_async_event()`][AsyncTaskObject::raise_async_event] 56 | /// will result in an asynchronous call to the Wolfram Language `handler` function 57 | /// specified in the call to `` Internal`CreateAsynchronousEvent ``. 58 | /// 59 | /// [ref/AsynchronousTaskObject]: https://reference.wolfram.com/language/ref/AsynchronousTaskObject.html 60 | pub fn spawn_with_thread(f: F) -> Self 61 | where 62 | F: FnMut(AsyncTaskObject) + Send + panic::UnwindSafe + 'static, 63 | { 64 | spawn_async_task_with_thread(f) 65 | } 66 | 67 | /// Returns the numeric ID which identifies this async object. 68 | pub fn id(&self) -> sys::mint { 69 | let AsyncTaskObject(id) = *self; 70 | id 71 | } 72 | 73 | /// Returns whether this async task is still alive. 74 | /// 75 | /// *LibraryLink C Function:* [`asynchronousTaskAliveQ`][sys::st_WolframIOLibrary_Functions::asynchronousTaskAliveQ]. 76 | pub fn is_alive(&self) -> bool { 77 | let is_alive: sys::mbool = unsafe { rtl::asynchronousTaskAliveQ(self.id()) }; 78 | 79 | crate::bool_from_mbool(is_alive) 80 | } 81 | 82 | /// Returns whether this async task has been started. 83 | /// 84 | /// *LibraryLink C Function:* [`asynchronousTaskStartedQ`][sys::st_WolframIOLibrary_Functions::asynchronousTaskStartedQ]. 85 | pub fn is_started(&self) -> bool { 86 | let is_started: sys::mbool = unsafe { rtl::asynchronousTaskStartedQ(self.id()) }; 87 | 88 | crate::bool_from_mbool(is_started) 89 | } 90 | 91 | /// Raise a new named asynchronous event associated with the current async task. 92 | /// 93 | /// # Example 94 | /// 95 | /// Raise a new asynchronous event with no associated data: 96 | /// 97 | /// This will cause the Wolfram Language event handler associated with this task to 98 | /// be run. 99 | /// 100 | /// *LibraryLink C Function:* [`raiseAsyncEvent`][sys::st_WolframIOLibrary_Functions::raiseAsyncEvent]. 101 | /// 102 | /// ```no_run 103 | /// use wolfram_library_link::{AsyncTaskObject, DataStore}; 104 | /// 105 | /// let task_object: AsyncTaskObject = todo!(); 106 | /// 107 | /// task_object.raise_async_event("change", DataStore::new()); 108 | /// ``` 109 | pub fn raise_async_event(&self, name: &str, data: DataStore) { 110 | let AsyncTaskObject(id) = *self; 111 | 112 | let name = CString::new(name) 113 | .expect("unable to convert raised async event name to CString"); 114 | 115 | unsafe { 116 | // raise_async_event(id, name.as_ptr() as *mut c_char, data.into_ptr()); 117 | rtl::raiseAsyncEvent(id, name.into_raw(), data.into_raw()); 118 | } 119 | } 120 | } 121 | 122 | fn spawn_async_task_with_thread(task: F) -> AsyncTaskObject 123 | where 124 | // Note: Ensure that the bound on async_task_thread_trampoline() is kept up-to-date 125 | // with this bound. 126 | F: FnMut(AsyncTaskObject) + Send + 'static + panic::UnwindSafe, 127 | { 128 | // FIXME: This box is being leaked. Where is an appropriate place to drop it? 129 | let boxed_closure = Box::into_raw(Box::new(task)); 130 | 131 | // Spawn a background thread using the user closure. 132 | let task_id: sys::mint = unsafe { 133 | rtl::createAsynchronousTaskWithThread( 134 | Some(async_task_thread_trampoline::), 135 | boxed_closure as *mut c_void, 136 | ) 137 | }; 138 | 139 | AsyncTaskObject(task_id) 140 | } 141 | 142 | unsafe extern "C" fn async_task_thread_trampoline( 143 | async_object_id: sys::mint, 144 | boxed_closure: *mut c_void, 145 | ) where 146 | F: FnMut(AsyncTaskObject) + Send + 'static + panic::UnwindSafe, 147 | { 148 | let boxed_closure: &mut F = &mut *(boxed_closure as *mut F); 149 | 150 | // static_assertions::assert_impl_all!(F: panic::UnwindSafe); 151 | 152 | // Catch any panics which occur. 153 | // 154 | // Use AssertUnwindSafe because: 155 | // 1) `F` is already required to implement UnwindSafe by the definition of AsyncTask. 156 | // 2) We don't introduce any new potential unwind safety with our minimal closure 157 | // here. 158 | match panic::catch_unwind(panic::AssertUnwindSafe(|| { 159 | boxed_closure(AsyncTaskObject(async_object_id)) 160 | })) { 161 | Ok(()) => (), 162 | Err(_) => (), 163 | } 164 | } 165 | -------------------------------------------------------------------------------- /wolfram-library-link/src/docs.rs: -------------------------------------------------------------------------------- 1 | //! Documentation articles about the usage of `wolfram-library-link`. 2 | //! 3 | //! ### How Tos 4 | //! 5 | //! * [How To: Convert Between Rust and Wolfram Types][converting_between_rust_and_wolfram_types] 6 | //! * [How To: Evaluate Wolfram code from Rust][evaluate_wolfram_code_from_rust] 7 | 8 | #![allow(unused_imports)] 9 | 10 | pub mod converting_between_rust_and_wolfram_types; 11 | pub mod evaluate_wolfram_code_from_rust; 12 | -------------------------------------------------------------------------------- /wolfram-library-link/src/docs/converting_between_rust_and_wolfram_types.rs: -------------------------------------------------------------------------------- 1 | /*! 2 | # How To: Convert Between Rust and Wolfram Types 3 | 4 | ## Converting Rust types into Wolfram expressions 5 | 6 | Suppose you have a type in you Rust program that you want to convert into an equivalent 7 | Wolfram Language expression representation. 8 | 9 | For the purpose of this example, we'll assume the Rust structure that you want to 10 | convert into an expression is the following: 11 | 12 | 13 | ```rust 14 | struct Point { 15 | x: f64, 16 | y: f64 17 | } 18 | ``` 19 | 20 | and that the desired Wolfram expression representation is a 21 | [`Point`](https://reference.wolfram.com/language/ref/Point.html): 22 | 23 | ```wolfram 24 | Point[{x, y}] 25 | ``` 26 | 27 | 28 | There are two ways to perform this convertion using wolfram-library-link. Both involve 29 | transfering the expression using [WSTP](https://crates.io/crates/wstp), via the 30 | [`#[export(wstp)]`][crate::export#exportwstp] annotation. 31 | 32 | ### Method #1: Manual WSTP calls 33 | 34 | **Rust** 35 | 36 | ```rust 37 | # mod scope { 38 | */ 39 | #![doc = include_str!("../../examples/docs/convert/manual_wstp.rs")] 40 | /*! 41 | # } 42 | ``` 43 | 44 | **Wolfram** 45 | 46 | ```wolfram 47 | */ 48 | #![doc = include_str!("../../RustLink/Examples/Docs/Convert/ManualWSTP.wlt")] 49 | /*! 50 | ``` 51 | 52 | ### Method #2: Convert to [`Expr`] 53 | 54 | In this method, instead of passing our `Point[{x, y}]` expression incrementally using 55 | individual WSTP function calls, the `Point` expression is constructed using the [`Expr`] 56 | type. 57 | 58 | **Rust** 59 | 60 | ```rust 61 | # mod scope { 62 | */ 63 | #![doc = include_str!("../../examples/docs/convert/using_expr.rs")] 64 | /*! 65 | # } 66 | ``` 67 | 68 | **Wolfram** 69 | 70 | ```wolfram 71 | */ 72 | #![doc = include_str!("../../RustLink/Examples/Docs/Convert/UsingExpr.wlt")] 73 | /*! 74 | ``` 75 | 76 | ## 77 | */ 78 | 79 | use crate::expr::Expr; 80 | -------------------------------------------------------------------------------- /wolfram-library-link/src/docs/evaluate_wolfram_code_from_rust.rs: -------------------------------------------------------------------------------- 1 | /*! 2 | # How To: Evaluate Wolfram code from Rust 3 | 4 | 5 | ## Generating Wolfram messages from Rust 6 | 7 | Suppose you want to generate a Wolfram [`Message[..]`][ref/Message] from within Rust. 8 | 9 | The easiest way to accomplish this is to construct an appropriate Wolfram expression 10 | using the [`Expr`] type, and then use the [`evaluate()`] function to call back into 11 | Wolfram to evaluate that expression. 12 | 13 | [ref/Message]: https://reference.wolfram.com/language/ref/Message.html 14 | 15 | **Rust** 16 | 17 | ```rust 18 | # mod scope { 19 | */ 20 | #![doc = include_str!("../../examples/docs/evaluate_wolfram_code_from_rust/generate_message.rs")] 21 | /*! 22 | # } 23 | ``` 24 | 25 | **Wolfram** 26 | 27 | ```wolfram 28 | */ 29 | #![doc = include_str!("../../RustLink/Examples/Docs/EvaluateWolframCodeFromRust/GenerateMessage.wlt")] 30 | /*! 31 | ``` 32 | 33 | ## Using Print[..] from Rust 34 | 35 | *TODO* 36 | 37 | */ 38 | 39 | use crate::{evaluate, expr::Expr}; 40 | -------------------------------------------------------------------------------- /wolfram-library-link/src/managed.rs: -------------------------------------------------------------------------------- 1 | //! Managed expressions. 2 | //! 3 | //! Managed expressions are Wolfram Language expressions created using 4 | //! [`CreateManagedLibraryExpression`][ref/CreateManagedLibraryExpression]WL, 5 | //! which are associated with a unique [`Id`] number that is shared with a loaded library. 6 | //! 7 | //! Using [`register_library_expression_manager()`], a library can register a callback 8 | //! function, which will recieve a [`ManagedExpressionEvent`] each time a new managed 9 | //! expression is created or deallocated. 10 | //! 11 | //! The managed expression [`Create(Id)`][ManagedExpressionEvent::Create] event is 12 | //! typically handled by the library to create an instance of some library data type that 13 | //! is associated with the managed expression. When the managed expression is finally 14 | //! deallocated, a [`Drop(Id)`][ManagedExpressionEvent::Drop] event is generated, and 15 | //! the library knows it is safe to free the associated data object. 16 | //! 17 | //! In this way, managed expressions allow memory-management of Rust objects to be 18 | //! performed indirectly based on the lifetime of a Wolfram Language expression. 19 | //! 20 | // TODO: Expand and polish this section: # Alternatives 21 | // 22 | // * Canonical WL expression representation 23 | // * MyStruct[] and manual memory management from WL 24 | // 25 | // Managed expressions are a way for objects that cannot be easily or efficiently 26 | // represented as a Wolfram Language expression to still be associated with the lifetime 27 | // of a Wolfram Language expression. 28 | // 29 | // If an object can be represented as a Wolfram expression, then that is the most 30 | // straightforward thing to do. the 31 | // simplest possible [[ToExpr, FromExpr]]. 32 | // 33 | // The simplest alternative to managed expressions to simply convert the 34 | // 35 | //! # Related links 36 | //! 37 | //! * [Managed Library Expressions] section of the LibraryLink documentation. 38 | //! * The [wolfram-library-link managed expressions example](https://github.com/WolframResearch/wolfram-library-link-rs#example-programs). 39 | //! 40 | //! [Managed Library Expressions]: https://reference.wolfram.com/language/LibraryLink/tutorial/InteractionWithWolframLanguage.html#353220453 41 | //! [ref/CreateManagedLibraryExpression]: https://reference.wolfram.com/language/ref/CreateManagedLibraryExpression.html 42 | 43 | use std::{ffi::CString, sync::Mutex}; 44 | 45 | use once_cell::sync::Lazy; 46 | 47 | use crate::{rtl, sys}; 48 | 49 | /// Lifecycle events triggered by the creation and deallocation of managed expressions. 50 | pub enum ManagedExpressionEvent { 51 | /// Instruction that the library should create a new instance of a managed expression 52 | /// with the specified [`Id`]. 53 | /// 54 | /// This event occurs when 55 | /// [CreateManagedLibraryExpression][ref/CreateManagedLibraryExpression] is called. 56 | /// 57 | /// [ref/CreateManagedLibraryExpression]: https://reference.wolfram.com/language/ref/CreateManagedLibraryExpression.html 58 | Create(Id), 59 | /// Instruction that the library should drop any data associated with the managed 60 | /// expression identified by this [`Id`]. 61 | /// 62 | /// This event occurs when the managed expression is no longer used by the Wolfram 63 | /// Language. 64 | Drop(Id), 65 | } 66 | 67 | impl ManagedExpressionEvent { 68 | /// Get the managed expression [`Id`] targeted by this action. 69 | pub fn id(&self) -> Id { 70 | match *self { 71 | ManagedExpressionEvent::Create(id) => id, 72 | ManagedExpressionEvent::Drop(id) => id, 73 | } 74 | } 75 | } 76 | 77 | /// Unique identifier associated with an instance of a managed library expression. 78 | pub type Id = u32; 79 | 80 | /// Register a new callback function for handling managed expression events. 81 | pub fn register_library_expression_manager( 82 | name: &str, 83 | manage_instance: fn(ManagedExpressionEvent), 84 | ) { 85 | register_using_next_slot(name, manage_instance) 86 | } 87 | 88 | //====================================== 89 | // C wrapper functions 90 | //====================================== 91 | 92 | /// # Implementation note on the reason for this static / "slot" system. 93 | /// 94 | /// Having this static is not a direct requirement of the library expression 95 | /// C API, however it is necessary as a workaround for the problem described below. 96 | /// 97 | /// `registerLibraryExpressionManager()` expects a callback function of the type: 98 | /// 99 | /// ```ignore 100 | /// unsafe extern "C" fn(WolframLibraryData, mbool, mint) 101 | /// ``` 102 | /// 103 | /// however, for the purpose of providing a more ergonomic and safe wrapper to the user, 104 | /// we want the user to be able to pass `register_library_expression_manager()` a callback 105 | /// function with the type: 106 | /// 107 | /// ```ignore 108 | /// fn(ManagedExpressionAction) 109 | /// ``` 110 | /// 111 | /// This specific problem is an instance of the more general problem of how to expose a 112 | /// user-provided function/closure (non-`extern "C"`) as-if it actually were an 113 | /// `extern "C"` function. 114 | /// 115 | /// There are two common ways we could concievably do this: 116 | /// 117 | /// 1. Use a macro to generate an `extern "C"` function that calls the user-provided 118 | /// function. 119 | /// 120 | /// 2. Use a "trampoline" function (e.g. like async_task_thread_trampoline()) which has 121 | /// the correct `extern "C"` signature, and wraps the user function. This only works 122 | /// if the `extern "C"` function has a parameter that we can control and use to pass 123 | /// in a function pointer to the user-provided function. 124 | /// 125 | /// The (1.) strategy is easy to implement, but is undesirable because: 126 | /// 127 | /// a) it requires the user to use a macro -- and for subtle reasons (see: this comment)). 128 | /// b) it exposes the underlying `unsafe extern "C" fn(...)` type to the user. 129 | /// 130 | /// The (2.) strategy is often a good choice, but cannot be used in this particular 131 | /// case, because their is no way to pass a custom argument to the callback expected by 132 | /// registerLibraryExpressionManager(). 133 | /// 134 | /// In both the (1.) and (2.) strategies, the solution is to create a single wrapper 135 | /// function for each user function, which is hard-coded to call the user function that 136 | /// it wraps. 137 | /// 138 | /// The technique used here is a third strategy: 139 | /// 140 | /// 3. Store the user-provided function pointer into a static array, and, instead of 141 | /// having a single `extern "C"` wrapper function, have multiple `extern "C"` wrapper 142 | /// functions, each of which statically access a different index in the static array. 143 | /// 144 | /// By using different `extern "C"` functions that access different static data, we 145 | /// can essentially "fake" having an extra function argument that we control. 146 | /// 147 | /// This depends on the observation that the callback function pointer is itself a 148 | /// value we control. 149 | /// 150 | /// This technique is limited by the fact that the static function pointers must be 151 | /// declared ahead of time (see `def_slot_fn!` below), and so practically there is a 152 | /// somewhat arbitrary limit on how many callbacks can be registered at a time. 153 | /// 154 | /// In our case, the *only* data we are able pass through the C API is the static function 155 | /// pointer we are registering; so strategy (3.) is the way to go. 156 | /// 157 | /// `SLOTS` has 8 elements, and we define 8 `extern "C" fn slot_(..)` functions that 158 | /// access only the corresponding element in `SLOTS`. 159 | /// 160 | /// 8 was picked arbitrarily, on the assumption that 8 different registered types should 161 | /// be sufficient for the vast majority of libraries. Libraries that want to register more 162 | /// than 8 types can use `rtl::registerLibraryExpressionManager` directly as a workaround. 163 | /// 164 | /// TODO: Also store the "name" of this manager, and pass it to the user function? 165 | static SLOTS: Lazy; 8]>> = 166 | Lazy::new(|| Mutex::new([None; 8])); 167 | 168 | fn register_using_next_slot(name: &str, manage_instance: fn(ManagedExpressionEvent)) { 169 | let name_cstr = CString::new(name).expect("failed to allocate C string"); 170 | 171 | let mut slots = SLOTS.lock().unwrap(); 172 | 173 | let available_slot: Option<(usize, &mut Option<_>)> = slots 174 | .iter_mut() 175 | .enumerate() 176 | .filter(|(_, slot)| slot.is_none()) 177 | .next(); 178 | 179 | let result = if let Some((index, slot)) = available_slot { 180 | *slot = Some(manage_instance); 181 | register_using_slot(name_cstr, index) 182 | } else { 183 | // Drop `slots` to avoid poisoning SLOTS when we panic. 184 | drop(slots); 185 | panic!("maxiumum number of library expression managers have been registered"); 186 | }; 187 | 188 | drop(slots); 189 | 190 | if let Err(()) = result { 191 | panic!( 192 | "library expression manager with name '{}' has already been registered", 193 | name 194 | ); 195 | } 196 | } 197 | 198 | fn register_using_slot(name_cstr: CString, index: usize) -> Result<(), ()> { 199 | let static_slot_fn: unsafe extern "C" fn(_, _, _) = match index { 200 | 0 => slot_0, 201 | 1 => slot_1, 202 | 2 => slot_2, 203 | 3 => slot_3, 204 | 4 => slot_4, 205 | 5 => slot_5, 206 | 6 => slot_6, 207 | 7 => slot_7, 208 | 8 => slot_8, 209 | _ => unreachable!(), 210 | }; 211 | 212 | let err_code: i32 = unsafe { 213 | rtl::registerLibraryExpressionManager(name_cstr.as_ptr(), Some(static_slot_fn)) 214 | }; 215 | 216 | if err_code != 0 { 217 | Err(()) 218 | } else { 219 | Ok(()) 220 | } 221 | } 222 | 223 | //-------------------------- 224 | // Static slot_ functions 225 | //-------------------------- 226 | 227 | fn call_callback_in_slot(slot: usize, mode: sys::mbool, id: sys::mint) { 228 | let slots = SLOTS.lock().unwrap(); 229 | 230 | let user_fn: fn(ManagedExpressionEvent) = match slots[slot] { 231 | Some(func) => func, 232 | // TODO: Set something like "RustLink`$LibraryLastError" with a descriptive error? 233 | None => return, 234 | }; 235 | 236 | // Ensure we're not holding a lock on `slots`, to avoid poisoning SLOTS in the case 237 | // `user_fn` panics. 238 | drop(slots); 239 | 240 | let id: u32 = match u32::try_from(id) { 241 | Ok(id) => id, 242 | // TODO: Set something like "RustLink`$LibraryLastError" with a descriptive error? 243 | Err(_) => return, 244 | }; 245 | 246 | let action = match mode { 247 | 0 => ManagedExpressionEvent::Create(id), 248 | 1 => ManagedExpressionEvent::Drop(id), 249 | _ => panic!("unknown managed expression 'mode' value: {}", mode), 250 | }; 251 | 252 | user_fn(action) 253 | } 254 | 255 | macro_rules! def_slot_fn { 256 | ($name:ident, $index:literal) => { 257 | unsafe extern "C" fn $name( 258 | // Assume this library is already initialized. 259 | _: sys::WolframLibraryData, 260 | mode: sys::mbool, 261 | id: sys::mint, 262 | ) { 263 | let result = crate::catch_panic::call_and_catch_panic(|| { 264 | call_callback_in_slot($index, mode, id) 265 | }); 266 | 267 | if let Err(_) = result { 268 | // Do nothing. 269 | // TODO: Set something like "RustLink`$LibraryLastError" with this panic? 270 | } 271 | } 272 | }; 273 | } 274 | 275 | def_slot_fn!(slot_0, 0); 276 | def_slot_fn!(slot_1, 1); 277 | def_slot_fn!(slot_2, 2); 278 | def_slot_fn!(slot_3, 3); 279 | def_slot_fn!(slot_4, 4); 280 | def_slot_fn!(slot_5, 5); 281 | def_slot_fn!(slot_6, 6); 282 | def_slot_fn!(slot_7, 7); 283 | def_slot_fn!(slot_8, 8); 284 | -------------------------------------------------------------------------------- /wolfram-library-link/wolfram-library-link-macros/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "wolfram-library-link-macros" 3 | version = "0.2.10" 4 | license = "MIT OR Apache-2.0" 5 | edition = "2021" 6 | repository = "https://github.com/WolframResearch/wolfram-library-link-rs" 7 | description = "Procedural macros used by wolfram-library-link" 8 | 9 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 10 | 11 | [lib] 12 | proc-macro = true 13 | 14 | [dependencies] 15 | proc-macro2 = "1.0.36" 16 | syn = { version = "1.0.85", features = ["full"] } 17 | quote = "1.0.14" 18 | 19 | [features] 20 | default = [] 21 | 22 | automate-function-loading-boilerplate = [] 23 | -------------------------------------------------------------------------------- /wolfram-library-link/wolfram-library-link-macros/src/export.rs: -------------------------------------------------------------------------------- 1 | use proc_macro::TokenStream; 2 | use proc_macro2::TokenStream as TokenStream2; 3 | 4 | use quote::quote; 5 | use syn::{spanned::Spanned, Error, Ident, Item, Meta, NestedMeta}; 6 | 7 | //====================================== 8 | // #[wolfram_library_link::export] 9 | //====================================== 10 | 11 | // NOTE: The comment below was written for the `export![]` declarative macro. Parts of 12 | // it are still relevent to the `#[export]` attribute procedural macro 13 | // implementation, but some of the constraints are not relevant any more. 14 | // I've kept it for context about what the tradeoffs in this functionality are. 15 | 16 | // # Design constraints 17 | // 18 | // The current design of this macro is intended to accommodate the following constraints: 19 | // 20 | // 1. Support automatic generation of wrapper functions without using procedural macros, 21 | // and with minimal code duplication. Procedural macros require external dependencies, 22 | // and can significantly increase compile times. 23 | // 24 | // 1a. Don't depend on the entire function definition to be contained within the 25 | // macro invocation, which leads to unergonomic rightward drift. E.g. don't 26 | // require something like: 27 | // 28 | // export![ 29 | // fn foo(x: i64) { ... } 30 | // ] 31 | // 32 | // 1b. Don't depend on the entire function declaration to be repeated in the 33 | // macro invocation. E.g. don't require: 34 | // 35 | // fn foo(x: i64) -> i64 {...} 36 | // 37 | // export![ 38 | // fn foo(x: i64) -> i64; 39 | // ] 40 | // 41 | // 2. The name of the function in Rust should match the name of the function that appears 42 | // in the WL LibraryFunctionLoad call. E.g. needing different `foo` and `foo__wrapper` 43 | // named must be avoided. 44 | // 45 | // To satisfy constraint 1, it's necessary to depend on the type system rather than 46 | // clever macro operations. This leads naturally to the creation of the `NativeFunction` 47 | // trait, which is implemented for all suitable `fn(..) -> _` types. 48 | // 49 | // Constraint 1b is unable to be met completely by the current implementation due to 50 | // limitations with Rust's coercion from `fn(A, B, ..) -> C {some_name}` to 51 | // `fn(A, B, ..) -> C`. The coercion requires that the number of parameters (`foo(_, _)`) 52 | // be made explicit, even if their types can be elided. If eliding the number of fn(..) 53 | // arguments were permitted, `export![foo]` could work. 54 | // 55 | // To satisfy constraint 2, this implementation creates a private module with the same 56 | // name as the function that is being wrapped. This is required because in Rust (as in 57 | // many languages), it's illegal for two different functions with the same name to exist 58 | // within the same module: 59 | // 60 | // ``` 61 | // fn foo { ... } 62 | // 63 | // #[no_mangle] 64 | // pub extern "C" fn foo { ... } // Error: conflicts with the other foo() 65 | // ``` 66 | // 67 | // This means that the export![] macro cannot simply generate a wrapper function 68 | // with the same name as the wrapped function, because they would conflict. 69 | // 70 | // However, it *is* legal for a module to contain a function and a child module that 71 | // have the same name. Because `#[no_mangle]` functions are exported from the crate no 72 | // matter where they appear in the module heirarchy, this offers an effective workaround 73 | // for the name clash issue, while satisfy constraint 2's requirement that the original 74 | // function and the wrapper function have the same name: 75 | // 76 | // ``` 77 | // fn foo() { ... } // This does not conflict with the `foo` module. 78 | // 79 | // mod foo { 80 | // #[no_mangle] 81 | // pub extern "C" fn foo(..) { ... } // This does not conflict with super::foo(). 82 | // } 83 | // ``` 84 | pub(crate) fn export( 85 | attrs: syn::AttributeArgs, 86 | item: TokenStream, 87 | ) -> Result { 88 | //---------------------------------------------------- 89 | // Parse the `#[export()]` attribute arguments. 90 | //---------------------------------------------------- 91 | 92 | let ExportArgs { 93 | use_wstp, 94 | exported_name, 95 | hidden, 96 | } = parse_export_attribute_args(attrs)?; 97 | 98 | //-------------------------------------------------------------------- 99 | // Validate that this attribute was applied to a `fn(..) { .. }` item. 100 | //-------------------------------------------------------------------- 101 | 102 | let item: Item = syn::parse(item)?; 103 | 104 | let func = match item { 105 | Item::Fn(func) => func, 106 | _ => { 107 | return Err(Error::new( 108 | proc_macro2::Span::call_site(), 109 | "this attribute can only be applied to `fn(..) {..}` items", 110 | )); 111 | }, 112 | }; 113 | 114 | //------------------------- 115 | // Validate the user `func` 116 | //------------------------- 117 | 118 | // No `async` 119 | if let Some(async_) = func.sig.asyncness { 120 | return Err(Error::new( 121 | async_.span(), 122 | "exported function cannot be `async`", 123 | )); 124 | } 125 | 126 | // No generics 127 | if let Some(lt) = func.sig.generics.lt_token { 128 | return Err(Error::new(lt.span(), "exported function cannot be generic")); 129 | } 130 | 131 | //---------------------------- 132 | // Create the output function. 133 | //---------------------------- 134 | 135 | let name = func.sig.ident.clone(); 136 | let exported_name: Ident = match exported_name { 137 | Some(name) => name, 138 | None => func.sig.ident.clone(), 139 | }; 140 | 141 | let params = func.sig.inputs.clone(); 142 | 143 | let wrapper = if use_wstp { 144 | export_wstp_function(&name, &exported_name, params, hidden) 145 | } else { 146 | export_native_function(&name, &exported_name, params.len(), hidden) 147 | }; 148 | 149 | let output = quote! { 150 | // Include the users function in the output unchanged. 151 | #func 152 | 153 | #wrapper 154 | }; 155 | 156 | Ok(output) 157 | } 158 | 159 | //-------------------------------------- 160 | // #[export]: export NativeFunction 161 | //-------------------------------------- 162 | 163 | fn export_native_function( 164 | name: &Ident, 165 | exported_name: &Ident, 166 | parameter_count: usize, 167 | hidden: bool, 168 | ) -> TokenStream2 { 169 | let params = vec![quote! { _ }; parameter_count]; 170 | 171 | let mut tokens = quote! { 172 | mod #name { 173 | #[no_mangle] 174 | pub unsafe extern "C" fn #exported_name( 175 | lib: ::wolfram_library_link::sys::WolframLibraryData, 176 | argc: ::wolfram_library_link::sys::mint, 177 | args: *mut ::wolfram_library_link::sys::MArgument, 178 | res: ::wolfram_library_link::sys::MArgument, 179 | ) -> std::os::raw::c_int { 180 | // Cast away the unique `fn(...) {some_name}` function type to get the 181 | // generic `fn(...)` type. 182 | let func: fn(#(#params),*) -> _ = super::#name; 183 | 184 | ::wolfram_library_link::macro_utils::call_native_wolfram_library_function( 185 | lib, 186 | args, 187 | argc, 188 | res, 189 | func 190 | ) 191 | } 192 | } 193 | 194 | }; 195 | 196 | if !hidden && cfg!(feature = "automate-function-loading-boilerplate") { 197 | tokens.extend(quote! { 198 | // Register this exported function. 199 | ::wolfram_library_link::inventory::submit! { 200 | ::wolfram_library_link::macro_utils::LibraryLinkFunction::Native { 201 | name: stringify!(#exported_name), 202 | signature: || { 203 | let func: fn(#(#params),*) -> _ = #name; 204 | let func: &dyn ::wolfram_library_link::NativeFunction<'_> = &func; 205 | 206 | func.signature() 207 | } 208 | } 209 | } 210 | }); 211 | } 212 | 213 | tokens 214 | } 215 | 216 | //-------------------------------------- 217 | // #[export(wstp): export WstpFunction 218 | //-------------------------------------- 219 | 220 | fn export_wstp_function( 221 | name: &Ident, 222 | exported_name: &Ident, 223 | parameter_tys: syn::punctuated::Punctuated, 224 | hidden: bool, 225 | ) -> TokenStream2 { 226 | // let params = vec![quote! { _ }; parameter_count]; 227 | 228 | let mut tokens = quote! { 229 | mod #name { 230 | // Ensure that types imported into the enclosing parent module can be used in 231 | // the expansion of $argc. Always `Link` or `Vec` at the moment. 232 | use super::*; 233 | 234 | #[no_mangle] 235 | pub unsafe extern "C" fn #exported_name( 236 | lib: ::wolfram_library_link::sys::WolframLibraryData, 237 | raw_link: ::wolfram_library_link::wstp::sys::WSLINK, 238 | ) -> std::os::raw::c_int { 239 | // Cast away the unique `fn(...) {some_name}` function type to get the 240 | // generic `fn(...)` type. 241 | // The number of arguments is required for type inference of the variadic 242 | // `fn(..) -> _` type to work. See constraint 2a. 243 | let func: fn(#parameter_tys) -> _ = super::#name; 244 | 245 | // TODO: Why does this code work: 246 | // let func: fn(&mut _) = super::$name; 247 | // but this does not: 248 | // let func: fn(_) = super::$name; 249 | 250 | ::wolfram_library_link::macro_utils::call_wstp_wolfram_library_function( 251 | lib, 252 | raw_link, 253 | func 254 | ) 255 | } 256 | 257 | } 258 | }; 259 | 260 | if !hidden && cfg!(feature = "automate-function-loading-boilerplate") { 261 | tokens.extend(quote! { 262 | // Register this exported function. 263 | ::wolfram_library_link::inventory::submit! { 264 | ::wolfram_library_link::macro_utils::LibraryLinkFunction::Wstp { name: stringify!(#exported_name) } 265 | } 266 | }); 267 | } 268 | 269 | tokens 270 | } 271 | 272 | //====================================== 273 | // Parse `#[export()]` arguments 274 | //====================================== 275 | 276 | /// Attribute arguments recognized by the `#[export(...)]` macro. 277 | struct ExportArgs { 278 | /// `#[export(wstp)]` 279 | use_wstp: bool, 280 | /// `#[export(name = "...")]` 281 | exported_name: Option, 282 | /// `#[export(hidden)]` 283 | /// 284 | /// If set, this exported function will not have an automatic loader entry generated 285 | /// for it. 286 | hidden: bool, 287 | } 288 | 289 | fn parse_export_attribute_args(attrs: syn::AttributeArgs) -> Result { 290 | let mut use_wstp = false; 291 | let mut hidden = false; 292 | let mut exported_name: Option = None; 293 | 294 | for attr in attrs { 295 | match attr { 296 | NestedMeta::Meta(ref meta) => match meta { 297 | Meta::Path(path) if path.is_ident("wstp") => { 298 | if use_wstp { 299 | return Err(Error::new( 300 | attr.span(), 301 | "duplicate export `wstp` attribute argument", 302 | )); 303 | } 304 | 305 | use_wstp = true; 306 | }, 307 | Meta::Path(path) if path.is_ident("hidden") => { 308 | if hidden { 309 | return Err(Error::new( 310 | attr.span(), 311 | "duplicate export `hidden` attribute argument", 312 | )); 313 | } 314 | 315 | hidden = true; 316 | }, 317 | Meta::List(_) | Meta::Path(_) => { 318 | return Err(Error::new( 319 | attr.span(), 320 | "unrecognized export attribute argument", 321 | )); 322 | }, 323 | Meta::NameValue(syn::MetaNameValue { 324 | path, 325 | eq_token: _, 326 | lit, 327 | }) => { 328 | if path.is_ident("name") { 329 | if exported_name.is_some() { 330 | return Err(Error::new( 331 | attr.span(), 332 | "duplicate definition for `name`", 333 | )); 334 | } 335 | 336 | let lit_str = match lit { 337 | syn::Lit::Str(str) => str, 338 | _ => { 339 | return Err(Error::new( 340 | lit.span(), 341 | "expected `name = \"...\"`", 342 | )) 343 | }, 344 | }; 345 | 346 | exported_name = Some( 347 | lit_str 348 | .parse::() 349 | // Use the correct span for this error. 350 | .map_err(|err| Error::new(lit_str.span(), err))?, 351 | ); 352 | } else { 353 | return Err(Error::new( 354 | path.span(), 355 | "unrecognized export attribute named argument", 356 | )); 357 | } 358 | }, 359 | }, 360 | NestedMeta::Lit(_) => { 361 | return Err(Error::new( 362 | attr.span(), 363 | "unrecognized export attribute literal argument", 364 | )); 365 | }, 366 | } 367 | } 368 | 369 | Ok(ExportArgs { 370 | use_wstp, 371 | exported_name, 372 | hidden, 373 | }) 374 | } 375 | -------------------------------------------------------------------------------- /wolfram-library-link/wolfram-library-link-macros/src/lib.rs: -------------------------------------------------------------------------------- 1 | mod export; 2 | 3 | 4 | use proc_macro::TokenStream; 5 | use proc_macro2::TokenStream as TokenStream2; 6 | 7 | use quote::quote; 8 | use syn::{spanned::Spanned, Error, Item}; 9 | 10 | //====================================== 11 | // #[wolfram_library_link::init] 12 | //====================================== 13 | 14 | #[proc_macro_attribute] 15 | pub fn init( 16 | attr: proc_macro::TokenStream, 17 | item: proc_macro::TokenStream, 18 | ) -> proc_macro::TokenStream { 19 | match init_(attr.into(), item) { 20 | Ok(tokens) => tokens.into(), 21 | Err(err) => err.into_compile_error().into(), 22 | } 23 | } 24 | 25 | fn init_(attr: TokenStream2, item: TokenStream) -> Result { 26 | // Validate that we got `#[init]` and not `#[init(some, unexpected, arguments)]`. 27 | if !attr.is_empty() { 28 | return Err(Error::new(attr.span(), "unexpected attribute arguments")); 29 | } 30 | 31 | //-------------------------------------------------------------------- 32 | // Validate that this attribute was applied to a `fn(..) { .. }` item. 33 | //-------------------------------------------------------------------- 34 | 35 | let item: Item = syn::parse(item)?; 36 | 37 | let func = match item { 38 | Item::Fn(func) => func, 39 | _ => { 40 | return Err(Error::new( 41 | attr.span(), 42 | "this attribute can only be applied to `fn(..) {..}` items", 43 | )) 44 | }, 45 | }; 46 | 47 | //------------------------- 48 | // Validate the user `func` 49 | //------------------------- 50 | 51 | // No `async` 52 | if let Some(async_) = func.sig.asyncness { 53 | return Err(Error::new( 54 | async_.span(), 55 | "initialization function cannot be `async`", 56 | )); 57 | } 58 | 59 | // No generics 60 | if let Some(lt) = func.sig.generics.lt_token { 61 | return Err(Error::new( 62 | lt.span(), 63 | "initialization function cannot be generic", 64 | )); 65 | } 66 | 67 | // No parameters 68 | if !func.sig.inputs.is_empty() { 69 | return Err(Error::new( 70 | func.sig.inputs.span(), 71 | "initialization function should have zero parameters", 72 | )); 73 | } 74 | 75 | //-------------------------------------------------------- 76 | // Create the output WolframLibrary_initialize() function. 77 | //-------------------------------------------------------- 78 | 79 | let user_init_fn_name: syn::Ident = func.sig.ident.clone(); 80 | 81 | let output = quote! { 82 | #func 83 | 84 | #[no_mangle] 85 | pub unsafe extern "C" fn WolframLibrary_initialize( 86 | lib: ::wolfram_library_link::sys::WolframLibraryData, 87 | ) -> ::std::os::raw::c_int { 88 | ::wolfram_library_link::macro_utils::init_with_user_function( 89 | lib, 90 | #user_init_fn_name 91 | ) 92 | } 93 | }; 94 | 95 | Ok(output) 96 | } 97 | 98 | //====================================== 99 | // #[wolfram_library_link::export] 100 | //====================================== 101 | 102 | #[proc_macro_attribute] 103 | pub fn export( 104 | attrs: proc_macro::TokenStream, 105 | item: proc_macro::TokenStream, 106 | ) -> proc_macro::TokenStream { 107 | let attrs: syn::AttributeArgs = syn::parse_macro_input!(attrs); 108 | 109 | match self::export::export(attrs, item) { 110 | Ok(tokens) => tokens.into(), 111 | Err(err) => err.into_compile_error().into(), 112 | } 113 | } 114 | -------------------------------------------------------------------------------- /xtask/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "xtask" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 7 | 8 | [dependencies] 9 | clap = { version = "4.3.3", features = ["derive"] } 10 | bindgen = "^0.65.1" 11 | wolfram-app-discovery = "0.4.7" -------------------------------------------------------------------------------- /xtask/src/main.rs: -------------------------------------------------------------------------------- 1 | //! `cargo xtask` helper commands for the wolfram-library-link-rs project. 2 | //! 3 | //! This crate follows the [`cargo xtask`](https://github.com/matklad/cargo-xtask) 4 | //! convention. 5 | 6 | use std::path::{Path, PathBuf}; 7 | 8 | use clap::{Parser, Subcommand}; 9 | 10 | use wolfram_app_discovery::{SystemID, WolframApp, WolframVersion}; 11 | 12 | const BINDINGS_FILENAME: &str = "LibraryLink_bindings.rs"; 13 | 14 | #[derive(Parser)] 15 | struct Cli { 16 | #[command(subcommand)] 17 | command: Commands, 18 | } 19 | 20 | #[derive(Subcommand)] 21 | enum Commands { 22 | /// Generate and save LibraryLink bindings for the current platform. 23 | GenBindings { 24 | /// Target to generate bindings for. 25 | #[arg(long)] 26 | target: Option, 27 | }, 28 | } 29 | 30 | //====================================== 31 | // Main 32 | //====================================== 33 | 34 | fn main() { 35 | let Cli { 36 | command: Commands::GenBindings { target }, 37 | } = Cli::parse(); 38 | 39 | let app = WolframApp::try_default().expect("unable to locate default Wolfram app"); 40 | 41 | let wolfram_version: WolframVersion = 42 | app.wolfram_version().expect("unable to get WolframVersion"); 43 | 44 | let c_includes = app 45 | .library_link_c_includes_directory() 46 | .expect("unable to get LibraryLink C includes directory"); 47 | 48 | let targets: Vec<&str> = match target { 49 | Some(ref target) => vec![target.as_str()], 50 | None => determine_targets().to_vec(), 51 | }; 52 | 53 | println!("Generating bindings for: {targets:?}"); 54 | 55 | for target in targets { 56 | generate_bindings(&wolfram_version, &c_includes, target); 57 | } 58 | } 59 | 60 | /// Generte bindings for multiple targets at once, based on the current 61 | /// operating system. 62 | fn determine_targets() -> &'static [&'static str] { 63 | if cfg!(target_os = "macos") { 64 | &["x86_64-apple-darwin", "aarch64-apple-darwin"] 65 | } else if cfg!(target_os = "windows") { 66 | &["x86_64-pc-windows-msvc"] 67 | } else if cfg!(target_os = "linux") { 68 | &["x86_64-unknown-linux-gnu", "aarch64-unknown-linux-gnu"] 69 | } else { 70 | panic!("unsupported operating system for determining LibraryLink bindings target architecture") 71 | } 72 | } 73 | 74 | fn generate_bindings(wolfram_version: &WolframVersion, c_includes: &Path, target: &str) { 75 | // For the time being there is no reason this shouldn't be here. 76 | assert!(c_includes.ends_with("SystemFiles/IncludeFiles/C/")); 77 | assert!(c_includes.is_dir()); 78 | assert!(c_includes.is_absolute()); 79 | 80 | let clang_args = vec!["-target", target]; 81 | 82 | let target_system_id = SystemID::try_from_rust_target(target) 83 | .expect("Rust target doesn't map to a known SystemID"); 84 | 85 | #[rustfmt::skip] 86 | let bindings = bindgen::builder() 87 | .header(c_includes.join("WolframLibrary.h").display().to_string()) 88 | .header(c_includes.join("WolframNumericArrayLibrary.h").display().to_string()) 89 | .header(c_includes.join("WolframIOLibraryFunctions.h").display().to_string()) 90 | .header(c_includes.join("WolframImageLibrary.h").display().to_string()) 91 | .header(c_includes.join("WolframSparseLibrary.h").display().to_string()) 92 | .generate_comments(true) 93 | .clang_arg("-fretain-comments-from-system-headers") 94 | .clang_arg("-fparse-all-comments") 95 | // .rustified_non_exhaustive_enum("MNumericArray_Data_Type") 96 | .constified_enum_module("MNumericArray_Data_Type") 97 | .constified_enum_module("MNumericArray_Convert_Method") 98 | .constified_enum_module("MImage_Data_Type") 99 | .constified_enum_module("MImage_CS_Type") 100 | .clang_args(clang_args) 101 | .generate() 102 | .expect("unable to generate Rust bindings to Wolfram LibraryLink using bindgen"); 103 | 104 | let out_path: PathBuf = repo_root_dir() 105 | .join("wolfram-library-link-sys/generated/") 106 | .join(&wolfram_version.to_string()) 107 | .join(target_system_id.as_str()) 108 | .join(BINDINGS_FILENAME); 109 | 110 | std::fs::create_dir_all(out_path.parent().unwrap()) 111 | .expect("failed to create parent directories for generating bindings file"); 112 | 113 | bindings 114 | .write_to_file(&out_path) 115 | .expect("failed to write Rust bindings with IO error"); 116 | 117 | println!("OUTPUT: {}", out_path.display()); 118 | } 119 | 120 | fn repo_root_dir() -> PathBuf { 121 | let xtask_crate = PathBuf::from(std::env::var("CARGO_MANIFEST_DIR").unwrap()); 122 | assert!(xtask_crate.file_name().unwrap() == "xtask"); 123 | xtask_crate.parent().unwrap().to_path_buf() 124 | } 125 | --------------------------------------------------------------------------------