├── .gitignore ├── LICENSE ├── README.md ├── chart.png ├── lib.rs ├── lib.wasm └── run.py /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | # C extensions 7 | *.so 8 | 9 | # Distribution / packaging 10 | .Python 11 | build/ 12 | develop-eggs/ 13 | dist/ 14 | downloads/ 15 | eggs/ 16 | .eggs/ 17 | lib/ 18 | lib64/ 19 | parts/ 20 | sdist/ 21 | var/ 22 | wheels/ 23 | pip-wheel-metadata/ 24 | share/python-wheels/ 25 | *.egg-info/ 26 | .installed.cfg 27 | *.egg 28 | MANIFEST 29 | 30 | # PyInstaller 31 | # Usually these files are written by a python script from a template 32 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 33 | *.manifest 34 | *.spec 35 | 36 | # Installer logs 37 | pip-log.txt 38 | pip-delete-this-directory.txt 39 | 40 | # Unit test / coverage reports 41 | htmlcov/ 42 | .tox/ 43 | .nox/ 44 | .coverage 45 | .coverage.* 46 | .cache 47 | nosetests.xml 48 | coverage.xml 49 | *.cover 50 | *.py,cover 51 | .hypothesis/ 52 | .pytest_cache/ 53 | 54 | # Translations 55 | *.mo 56 | *.pot 57 | 58 | # Django stuff: 59 | *.log 60 | local_settings.py 61 | db.sqlite3 62 | db.sqlite3-journal 63 | 64 | # Flask stuff: 65 | instance/ 66 | .webassets-cache 67 | 68 | # Scrapy stuff: 69 | .scrapy 70 | 71 | # Sphinx documentation 72 | docs/_build/ 73 | 74 | # PyBuilder 75 | target/ 76 | 77 | # Jupyter Notebook 78 | .ipynb_checkpoints 79 | 80 | # IPython 81 | profile_default/ 82 | ipython_config.py 83 | 84 | # pyenv 85 | .python-version 86 | 87 | # pipenv 88 | # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. 89 | # However, in case of collaboration, if having platform-specific dependencies or dependencies 90 | # having no cross-platform support, pipenv may install dependencies that don't work, or not 91 | # install all needed dependencies. 92 | #Pipfile.lock 93 | 94 | # PEP 582; used by e.g. github.com/David-OConnor/pyflow 95 | __pypackages__/ 96 | 97 | # Celery stuff 98 | celerybeat-schedule 99 | celerybeat.pid 100 | 101 | # SageMath parsed files 102 | *.sage.py 103 | 104 | # Environments 105 | .env 106 | .venv 107 | env/ 108 | venv/ 109 | ENV/ 110 | env.bak/ 111 | venv.bak/ 112 | 113 | # Spyder project settings 114 | .spyderproject 115 | .spyproject 116 | 117 | # Rope project settings 118 | .ropeproject 119 | 120 | # mkdocs documentation 121 | /site 122 | 123 | # mypy 124 | .mypy_cache/ 125 | .dmypy.json 126 | dmypy.json 127 | 128 | # Pyre type checker 129 | .pyre/ 130 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 Ezzeri Esa 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. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # bridge 2 | 3 | Simple examples for Python-Rust interop via WebAssembly. 4 | 5 | ## Commands 6 | 7 | To run microbenchmarks: 8 | ``` 9 | python run.py 10 | ``` 11 | 12 | To install `wasmtime` with `pip`: 13 | ``` 14 | pip install wasmtime 15 | ``` 16 | 17 | To recompile `microbenchmarks.wasm`: 18 | ``` 19 | rustup target add wasm32-wasi 20 | rustc lib.rs --codegen opt-level=3 --crate-type=cdylib --target=wasm32-wasi 21 | ``` 22 | 23 | ## Context 24 | 25 | Foreign function interfaces (FFI) allow programs written in one programming 26 | language to call routines in another language. This can be done through dynamic 27 | libraries i.e. `.dylib`/`.so`/`.dll` files for Mac/Linux/Windows respectively, 28 | as discussed in the post [here](https://codeburst.io/how-to-use-rust-to-extend-python-360174ee5819) 29 | re: using Python to call Rust. 30 | 31 | WebAssembly is a way of taking code written in programming languages other than 32 | JavaScript and running that code in the browser; this effectively means the 33 | compiled `.wasm` binary is agnostic to the choice of processor and OS. Lin Clark 34 | has an excellent intro series on WebAssembly [here](https://hacks.mozilla.org/2017/02/a-cartoon-intro-to-webassembly/). 35 | 36 | The WebAssembly project has expanded beyond the browser, with [wasmtime](https://wasmtime.dev) 37 | as the independent runtime and [WASI](https://wasi.dev) as the unified systems 38 | interface. This could potentially make the `.wasm` format the new standard in 39 | portable binaries for cross-language bridges. 40 | 41 | The example in this repo calculates in pure Python and in Python with Rust via 42 | WebAssembly (1) the 10,000th prime number, (2) pi, and (3) the 25th Fibonacci 43 | number, with local microbenchmarks as follows: 44 | 45 | ![microbenchmarks](/chart.png) 46 | 47 | Minimal examples to compile and run WebAssembly can be found [here](https://github.com/savarin/minimal). 48 | -------------------------------------------------------------------------------- /chart.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/savarin/bridge/25c959a402f72d31c2dbabeb6bf1665ea4160c80/chart.png -------------------------------------------------------------------------------- /lib.rs: -------------------------------------------------------------------------------- 1 | /// Calculates the nth prime number. 2 | #[no_mangle] 3 | pub extern fn prime(n: u32) -> u32 { 4 | if n == 0 { 5 | return 2 6 | } 7 | 8 | let m = n as usize; 9 | 10 | let mut primes = vec![3]; 11 | let mut recent = 3; 12 | let mut count = 1; 13 | 14 | while count < m { 15 | recent += 2; 16 | let mut is_prime = true; 17 | 18 | for item in &primes { 19 | if recent % item == 0 { 20 | is_prime = false; 21 | break; 22 | } 23 | } 24 | 25 | if is_prime { 26 | primes.push(recent); 27 | count += 1; 28 | } 29 | } 30 | 31 | primes[m - 1] 32 | } 33 | 34 | 35 | /// Calculates pi, as per https://github.com/JuliaLang/Microbenchmarks. 36 | #[no_mangle] 37 | pub extern fn pi() -> f64 { 38 | let mut sum = 0.; 39 | 40 | for _ in 0..500 { 41 | sum = (1..10001) 42 | .map(|k| { 43 | let k = k as f64; 44 | 1. / (k * k) 45 | }) 46 | .sum(); 47 | } 48 | 49 | sum 50 | } 51 | 52 | 53 | /// Calculates the nth Fibonacci number, as per https://github.com/JuliaLang/Microbenchmarks. 54 | #[no_mangle] 55 | pub extern fn fibonacci(n: i32) -> i32 { 56 | if n < 2 { 57 | n 58 | } else { 59 | fibonacci(n - 1) + fibonacci(n - 2) 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /lib.wasm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/savarin/bridge/25c959a402f72d31c2dbabeb6bf1665ea4160c80/lib.wasm -------------------------------------------------------------------------------- /run.py: -------------------------------------------------------------------------------- 1 | import time 2 | 3 | import wasmtime.loader 4 | 5 | import lib 6 | 7 | 8 | def prime(n): 9 | # type: (int) -> int 10 | """Calculates the nth prime number.""" 11 | if n == 0: 12 | return 2 13 | 14 | primes = [3] 15 | recent = 3 16 | count = 1 17 | 18 | while count < n: 19 | recent += 2 20 | is_prime = True 21 | 22 | for item in primes: 23 | if recent % item == 0: 24 | is_prime = False 25 | break 26 | 27 | if is_prime: 28 | primes.append(recent) 29 | count += 1 30 | 31 | return primes[n - 1] 32 | 33 | 34 | def pi(): 35 | # type: () -> float 36 | """Calculates pi, as per https://github.com/JuliaLang/Microbenchmarks""" 37 | sum = 0.0 38 | 39 | for j in range(1, 501): 40 | sum = 0.0 41 | 42 | for k in range(1, 10001): 43 | sum += 1.0 / (k * k) 44 | 45 | return sum 46 | 47 | 48 | def fibonacci(n): 49 | # type: (int) -> int 50 | """Calculates the nth Fibonacci number, as per https://github.com/JuliaLang/Microbenchmarks""" 51 | if n < 2: 52 | return n 53 | 54 | return fibonacci(n - 1) + fibonacci(n - 2) 55 | 56 | 57 | def benchmark(expression, iterations, variant): 58 | # type: (str, int, str) -> None 59 | """Run benchmark on expression eval.""" 60 | start = time.time() 61 | 62 | for i in range(iterations): 63 | result = eval(expression) 64 | 65 | average = (time.time() - start) / iterations 66 | print("average time (out of {}) {}: {:3f}s".format(iterations, variant, average)) 67 | 68 | 69 | if __name__ == "__main__": 70 | print("Calculating 10,000th prime number") 71 | assert prime(10000) == 104743 72 | assert lib.prime(10000) == 104743 73 | benchmark("prime(10000)", 3, "Python") 74 | benchmark("lib.prime(10000)", 3, "Rust") 75 | 76 | print("\nCalculating pi") 77 | assert abs(pi() - 1.644834071848065) < 1e-6 78 | assert abs(lib.pi() - 1.644834071848065) < 1e-6 79 | benchmark("pi()", 3, "Python") 80 | benchmark("lib.pi()", 3, "Rust") 81 | 82 | print("\nCalculating 25th Fibonacci number") 83 | assert fibonacci(25) == 75025 84 | assert lib.fibonacci(25) == 75025 85 | benchmark("fibonacci(25)", 3, "Python") 86 | benchmark("lib.fibonacci(25)", 3, "Rust") 87 | --------------------------------------------------------------------------------