From a9addfe6b02c8ed1434e83ca041b7b9617d125ba Mon Sep 17 00:00:00 2001 From: Ashwin Naren Date: Fri, 28 Feb 2025 17:29:50 -0800 Subject: [PATCH 1/2] rewrite of the book --- src/SUMMARY.md | 9 +- src/getting_started/index.md | 4 + src/getting_started/initial_setup.md | 73 ++++++++++++++++ src/getting_started/installation.md | 40 +++++++++ src/interop/index.md | 3 + src/interop/python_from_rust.md | 2 + src/interop/rust_from_python.md | 121 +++++++++++++++++++++++++++ src/introduction/index.md | 3 + src/writing_functions.md | 45 ---------- 9 files changed, 253 insertions(+), 47 deletions(-) create mode 100644 src/getting_started/index.md create mode 100644 src/getting_started/initial_setup.md create mode 100644 src/getting_started/installation.md create mode 100644 src/interop/index.md create mode 100644 src/interop/python_from_rust.md create mode 100644 src/interop/rust_from_python.md create mode 100644 src/introduction/index.md delete mode 100644 src/writing_functions.md diff --git a/src/SUMMARY.md b/src/SUMMARY.md index e974727..7bbc211 100644 --- a/src/SUMMARY.md +++ b/src/SUMMARY.md @@ -1,4 +1,9 @@ # Summary -- [Introduction](./intro.md) -- [Writing Functions](./writing_functions.md) +- [Introduction](introduction/index.md) +- [Getting Started](getting_started/index.md) + - [Installation](getting_started/installation.md) + - [Initial Setup](getting_started/initial_setup.md) +- [Interop](interop/index.md) + - [Calling Rust from Python](interop/rust_from_python.md) + - [Calling Python from Rust](interop/python_from_rust.md) diff --git a/src/getting_started/index.md b/src/getting_started/index.md new file mode 100644 index 0000000..071944d --- /dev/null +++ b/src/getting_started/index.md @@ -0,0 +1,4 @@ +# Getting Started + +- [Installation](./installation.md) +- [Initial Setup](./initial_setup.md) \ No newline at end of file diff --git a/src/getting_started/initial_setup.md b/src/getting_started/initial_setup.md new file mode 100644 index 0000000..35c5818 --- /dev/null +++ b/src/getting_started/initial_setup.md @@ -0,0 +1,73 @@ +# Initial Setup + +First `rustpython_vm` needs to be imported. +If `rustpython` is installed, it can be imported as a re-export: +```rust +use rustpython::vm; +``` + +if `rustpython_vm` is installed, it can be imported just like any other module. + +```rust +use rustpython::vm; + +fn main() -> vm::PyResult<()> { + vm::Interpreter::without_stdlib(Default::default()).enter(|vm| { + let scope = vm.new_scope_with_builtins(); + let source = r#"print("Hello World!")"#; + let code_obj = vm + .compile(source, vm::compiler::Mode::Exec, "".to_owned()) + .map_err(|err| vm.new_syntax_error(&err, Some(source)))?; + + vm.run_code_obj(code_obj, scope)?; + + Ok(()) + }) +} +``` + +This will print `Hello World!` to the console. + +## Adding the standard library +If the `stdlib` feature is enabled, +the standard library can be added to the interpreter by calling `add_native_modules` +with the result of `rustpython_stdlib::get_module_inits()`. +```rust +use rustpython::vm as vm; +use std::process::ExitCode; +use vm::{Interpreter, builtins::PyStrRef}; + +fn py_main(interp: &Interpreter) -> vm::PyResult { + interp.enter(|vm| { + let scope = vm.new_scope_with_builtins(); + let source = r#"print("Hello World!")"#; + let code_obj = vm + .compile(source, vm::compiler::Mode::Exec, "".to_owned()) + .map_err(|err| vm.new_syntax_error(&err, Some(source)))?; + + vm.run_code_obj(code_obj, scope)?; + }) +} + +fn main() -> ExitCode { + // Add standard library path + let mut settings = vm::Settings::default(); + settings.path_list.push("Lib".to_owned()); + let interp = vm::Interpreter::with_init(settings, |vm| { + vm.add_native_modules(rustpython_stdlib::get_module_inits()); + }); + let result = py_main(&interp); + let result = result.map(|result| { + println!("name: {result}"); + }); + ExitCode::from(interp.run(|_vm| result)) +} +``` + +to import a module, the following code can be used: +```rust, no_run +// Add local library path +vm.insert_sys_path(vm.new_pyobj("")) + .expect("add examples to sys.path failed"); +let module = vm.import("", 0)?; +``` diff --git a/src/getting_started/installation.md b/src/getting_started/installation.md new file mode 100644 index 0000000..d7ae156 --- /dev/null +++ b/src/getting_started/installation.md @@ -0,0 +1,40 @@ +# Installation +## Requirements +RustPython requires Rust latest stable version to be installed + +## Stable +The latest stable version of the library can be installed using the following command: +```bash +cargo add rustpython +``` + +or by adding the following to your `Cargo.toml`: +```toml +[dependencies] +rustpython = "0.4" +``` + +## Nightly +Nightly releases are built weekly and are released on git. +```toml +[dependencies] +rustpython = { git = "https://github.com/RustPython/RustPython", tag = "2025-02-24-main-13" } +``` + +The tag should be pointed to the latest tag found at https://github.com/RustPython/RustPython/tags. + +## Features +By default `threading`, `stdlib`, and `importlib` are enabled. +### `bz2` +If you'd like to use the `bz2` module, you can enable the `bz2` feature. +### `stdlib` +`stdlib` is the default feature that enables the standard library. +### `sqlite` +If you'd like to use the `sqlite3` module, you can enable the `sqlite` feature. +### `ssl` +If you'd like to make https requests, you can enable the ssl feature, +which also lets you install the pip package manager. +Note that on Windows, you may need to install OpenSSL, or you can enable the ssl-vendor feature instead, +which compiles OpenSSL for you but requires a C compiler, perl, and make. +OpenSSL version 3 is expected and tested in CI. Older versions may not work. + diff --git a/src/interop/index.md b/src/interop/index.md new file mode 100644 index 0000000..d02aad6 --- /dev/null +++ b/src/interop/index.md @@ -0,0 +1,3 @@ +# Interpretation between Rust and Python +- [Calling Rust from Python](./rust_from_python.md) +- [Calling Python from Rust](./python_from_rust.md) diff --git a/src/interop/python_from_rust.md b/src/interop/python_from_rust.md new file mode 100644 index 0000000..dae5f91 --- /dev/null +++ b/src/interop/python_from_rust.md @@ -0,0 +1,2 @@ +# Calling Python from Rust +TODO. \ No newline at end of file diff --git a/src/interop/rust_from_python.md b/src/interop/rust_from_python.md new file mode 100644 index 0000000..e383f05 --- /dev/null +++ b/src/interop/rust_from_python.md @@ -0,0 +1,121 @@ +# Calling Rust from Python +## Structure +```rust, ignore +use rustpython::vm::pymodule; +#[pymodule] +mod test_module { + #[pyattr] + pub const THE_ANSWER: i32 = 42; + + #[pyfunction] + pub fn add(a: i32, b: i32) -> i32 { + a + b + } + + #[pyattr] + #[pyclass] + pub struct TestClass { + pub value: i32, + } + + #[pyclass] + impl TestClass { + #[pygetset] + pub fn value(&self) -> i32 { + self.value + } + + #[pymethod] + pub fn get_info(&self) -> i32 { + self.value * 2 + } + } +} +``` +This code defines a Python module named `test_module` with two items: +a constant named `THE_ANSWER` and a function named `add`. +The `#[pymodule]` attribute is used to mark the module, +and the `#[pyattr]` and `#[pyfunction]` attributes are used to mark the constant and function, respectively. + +RustPython allows for 3 types of items in a module: +- Variables: Defined using the `#[pyattr]` attribute. +- Functions: Defined using the `#[pyfunction]` attribute. +- Classes: Defined using the `#[pyclass]` attribute. + +## General Configuration +Most attributes have a `name` parameter that can be used to specify the name of the item in Python. +If the `name` parameter is not provided, the Rust identifier is used as the name in Python. + +## Variables +Variables are defined using the `#[pyattr]` attribute. +A variable can either be a constant or a function. +Note that classes are treated as attributes in RustPython +and are annotated with `#[pyattr]` as well, but that can be done away with if needed. +```rust, no_run +#[pyattr] +const THE_ANSWER: i32 = 42; +// ... or +#[pyattr] +fn the_answer() -> i32 { + 42 +} +// ... or +// this will cache the result of the function +// and return the same value every time it is called +#[pyattr(once)] +fn cached_answer() -> i32 { + 42 +} +``` + +## Valid Arguments + +Every return value must be convertible to PyResult. This is defined as IntoPyResult trait. So any return value of them must implement IntoPyResult. It will be PyResult, PyObjectRef and any PyResult when T implements IntoPyObject. Practically we can list them like: + + +The `vm` paramter is just suffix. We add it as the last parameter unless we don't use `vm` at all - very rare case. It takes an object `obj` as `PyObjectRef`, which is a general python object. It returns `PyResult`, which will turn into `PyResult` the same representation of `PyResult`. + +Every return value must be convertible to `PyResult`. This is defined as `IntoPyResult` trait. So any return value of them must implement `IntoPyResult`. It will be `PyResult`, `PyObjectRef` and any `PyResult` when T implements `IntoPyObject`. Practically we can list them like: +- Any `T` when `PyResult` is possible +- `PyObjectRef` +- `PyResult<()>` and `()` as `None` +- `PyRef` like `PyIntRef`, `PyStrRef` +- `T: PyValue` like `PyInt`, `PyStr` +- Numbers like `usize` or `f64` for `PyInt` and `PyFloat` +- `String` for `PyStr` +- And more types implementing `IntoPyObject`. + +The same could mostly be said about input arguments. + +## Functions +Functions are defined using the `#[pyfunction]` attribute. +```rust, no_run +#[pyfunction] +fn add(a: i32, b: i32) -> i32 { + a + b +} +``` + +## Classes +Classes are defined using the `#[pyclass]` attribute. +```rust, no_run +#[pyclass] +pub struct TestClass { + pub value: i32, +} +#[pyclass] +impl TestClass { +} +``` +### Associated Data +TODO. +### Methods +TODO. +### Getters and Setters +TODO. +### Class Methods +TODO. +### Static Methods +TODO. +### Inheritance +TODO. diff --git a/src/introduction/index.md b/src/introduction/index.md new file mode 100644 index 0000000..5ff8c66 --- /dev/null +++ b/src/introduction/index.md @@ -0,0 +1,3 @@ +# Introduction + +RustPython is a Python interpreter written in Rust. diff --git a/src/writing_functions.md b/src/writing_functions.md deleted file mode 100644 index 16d92c8..0000000 --- a/src/writing_functions.md +++ /dev/null @@ -1,45 +0,0 @@ -# Writing Python functions in Rust - -In RustPython, it's possible to write functions in Rust and import them into and call them from -Python. Here's an example: - -```rust -fn rustmod_funct( - obj: PyObjectRef, - s: PyStringRef, - maybe_thing: OptionalArg, - vm: &VirtualMachine, -) -> PyResult<(String, Vec)> { ... } -``` - -## Parameters - -You can use any type that implements [`FromArgs`] as a parameter to your function, which includes -types that implements [`TryFromObject`]. In our example, we use a standard `PyObjectRef`, a -`PyStringRef` (which is really just a `PyRef`, and `PyRef` implements `TryFromObject`), -and an `OptionalArg`, where [`OptionalArg`] gets an optional positional argument. In -addition, [`TryFromObject`] is implemented for every primitive number type, so you can use those as -args too. -[Here is a list of all of the types that implement `TryFromObject`](https://docs.rs/rustpython-vm/0.1.1/rustpython_vm/pyobject/trait.TryFromObject.html#foreign-impls). - -## VirtualMachine parameter - -You can optionally put a `vm: &VirtualMachine` parameter at the end of the parameter list in order to -get access to the Python VM. If you're doing anything more than a very simple function, you'll likely -want this, as it is necessary for creating exception objects; but, if it turns out you didn't use it -in the function, always remember that you can just remove it. - -## Return type - -You can put any type that implements [`IntoPyObject`] as the return type of your function, which includes -many simple Rust types that you'd expect to map cleanly to Python, e.g. `String` -> `str`, `Vec` -> `bytes`, -integral primitives -> `int`, `f32,f64` -> float. If you need to return an error from the function, you can -put any [`IntoPyObject`] type as the `T` in [`PyResult`], and return an `Err(exc)`. (You can create the -exception using one of the `new_*_error` methods on [`VirtualMachine`]) - -[`FromArgs`]: https://docs.rs/rustpython-vm/*/rustpython_vm/function/trait.FromArgs.html -[`TryFromObject`]: https://docs.rs/rustpython-vm/*/rustpython_vm/pyobject/trait.TryFromObject.html -[`OptionalArg`]: https://docs.rs/rustpython-vm/*/rustpython_vm/function/enum.OptionalArg.html -[`IntoPyObject`]: https://docs.rs/rustpython-vm/*/rustpython_vm/pyobject/trait.IntoPyObject.html -[`PyResult`]: https://docs.rs/rustpython-vm/*/rustpython_vm/pyobject/type.PyResult.html -[`VirtualMachine`]: https://docs.rs/rustpython-vm/*/rustpython_vm/struct.VirtualMachine.html From 773c41aba14daaecafd3929e81b5aceb11d1485b Mon Sep 17 00:00:00 2001 From: Ashwin Naren Date: Thu, 3 Apr 2025 07:54:09 -0700 Subject: [PATCH 2/2] more docs --- src/interop/rust_from_python.md | 68 ++++++++++++++++++++++++++++----- 1 file changed, 59 insertions(+), 9 deletions(-) diff --git a/src/interop/rust_from_python.md b/src/interop/rust_from_python.md index e383f05..4268b20 100644 --- a/src/interop/rust_from_python.md +++ b/src/interop/rust_from_python.md @@ -68,14 +68,8 @@ fn cached_answer() -> i32 { } ``` -## Valid Arguments - -Every return value must be convertible to PyResult. This is defined as IntoPyResult trait. So any return value of them must implement IntoPyResult. It will be PyResult, PyObjectRef and any PyResult when T implements IntoPyObject. Practically we can list them like: - - -The `vm` paramter is just suffix. We add it as the last parameter unless we don't use `vm` at all - very rare case. It takes an object `obj` as `PyObjectRef`, which is a general python object. It returns `PyResult`, which will turn into `PyResult` the same representation of `PyResult`. - -Every return value must be convertible to `PyResult`. This is defined as `IntoPyResult` trait. So any return value of them must implement `IntoPyResult`. It will be `PyResult`, `PyObjectRef` and any `PyResult` when T implements `IntoPyObject`. Practically we can list them like: +## Valid Arguments/Return Types +Every input and return value must be convertible to `PyResult`. This is defined as `IntoPyResult` trait. So any return value of them must implement `IntoPyResult`. It will be `PyResult`, `PyObjectRef` and any `PyResult` when T implements `IntoPyObject`. Practically we can list them like: - Any `T` when `PyResult` is possible - `PyObjectRef` - `PyResult<()>` and `()` as `None` @@ -85,7 +79,63 @@ Every return value must be convertible to `PyResult`. This is defined as `IntoPy - `String` for `PyStr` - And more types implementing `IntoPyObject`. -The same could mostly be said about input arguments. +The `vm` paramter is optional. We add it as the last parameter unless we don't use `vm` at all - very rare case. It takes an object `obj` as `PyObjectRef`, which is a general python object. It returns `PyResult`, which will turn into `PyResult` the same representation of `PyResult`. The `vm` parameter does not need to be passed in by the python code. + +If needed a seperate struct can be used for arguments using the `FromArgs` trait like so: + +```rust +#[derive(FromArgs)] +struct BisectArgs { + a: PyObjectRef, + x: PyObjectRef + #[pyarg(any, optional)] + lo: OptionalArg, + #[pyarg(any, optional)] + hi: OptionalArg, + #[pyarg(named, default)] + key: Option, +} + +#[pyfunction] +fn bisect_left( + BisectArgs { a, x, lo, hi, key }: BisectArgs, + vm: &VirtualMachine, +) -> PyResult { + // ... +} + +// or ... + +#[pyfunction] +fn bisect_left( + args: BisectArgs, + vm: &VirtualMachine, +) -> PyResult { + // ... +} +``` + +## Errors + +Returning a PyResult is the supported error handling strategy. Builtin python errors are created with `vm.new_xxx_error` methods. + +### Custom Errors + +``` +#[pyattr(once)] +fn error(vm: &VirtualMachine) -> PyTypeRef { + vm.ctx.new_exception_type( + "", + "", + Some(vec![vm.ctx.exceptions.exception_type.to_owned()]), + ) +} + +// convenience function +fn new_error(message: &str, vm: &VirtualMachine) -> PyBaseExceptionRef { + vm.new_exception_msg(vm.class("", ""), message.to_owned()) +} +``` ## Functions Functions are defined using the `#[pyfunction]` attribute.