Open oosavu opened 2 months ago
The reason this is a bit silly mostly has to do with registered functions not having direct access to the steel runtime - they're more or less pure. There are ways to do that but I wouldn't consider those stable enough to rely upon.
So, alternatively, there are two ways to go about this. You can handle it in rust space, or you can handle it in user space within some steel boiler plate. Both have their trade offs, but here are both in the event you choose to go with one over the other:
Handling this in user space:
;; Define functions in steel code that pass the object through
;; manually, and just have the global object and functions be registered
;; in a module and use those.
(define (MyLittleObject name)
(#%MyLittleObject #%global-object name))
This approach would require that all of your functions accept the global object as well as the additional parameters, and then you handle wrapping them up in steel code.
Alternatively, if you 100% know that your object is global and static, you could do something like this:
struct MyGlobalCoreObject {}
impl MyGlobalCoreObject {
fn my_global_function(parameter: &str) {}
}
struct MyLittleObject {}
impl MyLittleObject {
pub fn new(name: &str) -> Self {
// Here i want to have access to the MyGlobalCoreObject and call my_global_function(name).
// How to pass it to the MyLittleObject constructor?
// do we need to pass the MyGlobalCoreObject here too? How to do it?
MyLittleObject {}
}
}
pub fn main() {
let mut vm = Engine::new();
vm.register_type::<MyLittleObject>("MyLittleObject?");
// You might not need to do this if your object itself is cheaply clonable - for example if under the
// hood it is already like Arc<T> or whatever, then you don't need to push it into a steel val first.
let global_object: MyGlobalCoreObject = {}.into_steelval();
vm.register_value("global_object", global_object);
// Register a closure to move the value in directly, and then we can reference it here.
// Alternatively, you could juse a lazy static or thread local as well in order to use pure functions.
vm.register_fn("MyLittleObject", move |name: SteelString| {
let global = global_object.clone();
let underlying = MyGlobalCoreObject::as_ref(&global).unwrap(); // You'll need to import the trait here
// do whatever you'd like with your &underlying (or &mut - just use as_ref_mut)
});
// etc
}
Here is an example of the as_ref
usage I mentioned above https://github.com/mattwparas/steel/blob/master/crates/steel-core/src/primitives/tcp.rs#L41
mattwparas, thank you for the fast answer!!! (and thank you for this awesome project! :) ) Both approaches is suitable for me. But the first one looks better. Can you please clarify some points about it? What type should i pass in 'MyLittleObject::new' on a rust side? It must be 'SteelVal'? How to coerce it to MyGlobalCoreObject on a rust side? I also need to call 'as_ref' function?
You should be able to do something like this:
struct MyGlobalCoreObject {}
impl Custom for MyGlobalCoreObject {}
impl MyGlobalCoreObject {
fn my_global_function(parameter: &str) {}
}
struct MyLittleObject {}
impl MyLittleObject {
pub fn new(global: &MyGlobalCoreObject, name: &str) -> Self {
/// etc
}
}
The register_fn
should do the proper type unwrapping for you
Is this resolved? Happy to answer any other questions if there are any
Hi! Sorry for the long reply, i was on vacation. Yes, this particular problem was solved! I am attaching a working proof-of-concept example, if somebody interesting:
#![allow(dead_code)]
#![allow(unused)]
use steel::steel_vm::engine::Engine;
use steel::steel_vm::register_fn::RegisterFn;
use steel_derive::Steel;
// In order to register a type with Steel,
// it must implement Clone, Debug, and Steel
#[derive(Clone, Debug, Steel, PartialEq)]
pub struct ExternalStruct {
foo: usize,
bar: String,
baz: f64,
}
#[derive(Clone, Debug, Steel, PartialEq)]
struct MyGlobalCoreObject {}
impl MyGlobalCoreObject {
pub fn my_global_function(&self, parameter: &str) -> i32 {
23
}
}
#[derive(Clone, Debug, Steel, PartialEq)]
struct MyLittleObject {
val: i32,
}
impl MyLittleObject {
pub fn new(global: &MyGlobalCoreObject) -> Self {
MyLittleObject { val: global.my_global_function("foobarbaz")}
}
pub fn get_val(&self) -> i32 {
self.val
}
}
pub fn main() {
let mut vm = Engine::new();
vm.register_type::<MyLittleObject>("MyLittleObject?");
vm.register_fn("MyLittleObject", MyLittleObject::new);
vm.register_fn("get_val", MyLittleObject::get_val);
let global_object: MyGlobalCoreObject = MyGlobalCoreObject {};
vm.register_external_value("global_object", global_object)
.unwrap();
// how to implicitly pass the global_object here?
let out = vm
.compile_and_run_raw_program(
r#"
(define my_little_object (MyLittleObject global_object))
(get_val my_little_object)
"#,
).unwrap();
println!("get_output: {out:?}");
}
I'm not sure that this topic is suitable for this, but anyway i wanna ask you: now I'm trying to understand what specific Lisp features are used in the Steel dialect and how to conveniently apply them to my task. Along the way, I realized that I don't know many features of modern Lisp dialects (such as Rocket). I'm reading a book on Rocket and trying to understand whether each specific feature is present in Steel. I see that the documentation for the language is in progress, so reading Rocket is the only way for me to understand all the features of Steel. tell me, to what extent is Steel similar to Rocket?
Sorry, I forgot to reply to this!
Steel is very much inspired by Racket. I don't quite have a list of comparison features, and slowly working on making sure that everything is documented.
Steel has some built in support for contracts, which are not as sophisticated as Racket's, but are there enough to ease the process of using them.
Steel is run in a byte code interpreter, with first class support for embedding. Racket typically compiles to chez scheme, so racket is typically much faster than steel. That being said, steel has support for native threads now, which I don't know if Racket does - typically with Racket you use futures (forgive me, I could be wrong on this) - and if you want true multi processing you use places.
Steel is aiming to be compliant with the R7RS spec - and is making good progress on it. I don't have an ETA on when it will be compliant, nor know if it will ever be truly compliant to the point where you can arbitrarily take scheme code and run it without having to make edits, but hopefully the translation process is minor.
Hi! I am trying to use Steel in my project (system for musical livecoding). I want to be able to create custom objects in Steel virtual machine similar to register_types.rs example file. But these objects must have access to another global object in my enviroment. But i do not understand how to do it right! I am not able to declare MyGlobalCoreObject as a global static value for my rust program (and it is antipattern of course...), so how to access it?
Here is simplified example: