Register a SA Forums Account here!
JOINING THE SA FORUMS WILL REMOVE THIS BIG AD, THE ANNOYING UNDERLINED ADS, AND STUPID INTERSTITIAL ADS!!!

You can: log in, read the tech support FAQ, or request your lost password. This dumb message (and those ads) will appear on every screen until you register! Get rid of this crap by registering your own SA Forums Account and joining roughly 150,000 Goons, for the one-time price of $9.95! We charge money because it costs us money per month for bills, and since we don't believe in showing ads to our users, we try to make the money back through forum registrations.
 
  • Post
  • Reply
Linear Zoetrope
Nov 28, 2011

A hero must cook

gonadic io posted:

I own a [T; 8] and would like to access the Ts out of it without copying (this is important). Then I'd like to call a FnOnce(T) -> T on each one and package them back up in an array.

I'm very confused by these requirements. Can you elaborate? For small data types, copying a pointer is likely to be no faster (and possibly slower) than copying. For large data types, it will probably have the compiler optimize it to a pointer chase rather than a full copy anyway.

Also, is it a different array you're packaging the results in or the same one? Because if it's a different one you at LEAST need to require Clone, otherwise you're just re-implementing Clone with unsafe.

Basically, I don't understand why your requirements are what they are, and why you can't use an FnMut.

I know there are some issues with being unable to move out of mutable references, so

code:
for v in array.iter_mut() {
    *v = fn(*v)
}
Won't work, but you can get around that with the `replace`, `uninitialized` trick you're using.

Linear Zoetrope fucked around with this message at 23:48 on Apr 20, 2016

Adbot
ADBOT LOVES YOU

gonadic io
Feb 16, 2011

>>=
It might well entirely be that I'm trying to do the wrong thing here too.

The core problem is that I have a large tree of nested box pointers to SVO<Registered>, and I'd like to end with SVO<Unregistered>. Given that this is a potentially huge structure I'd like to do it with as little copying as possible so I've got deregister taking self by value as I'll never want to access the old SVO.

The leaf case is easy, but given the array [Box<SVO<Registered>>; 8] I'd like to call deregister on each of them recursively (to get 8 Box<SVO<Unregistered>>s) and then collect them up. Given that it's only 8 pointers I'm not that fussed about reusing the old array's memory (but I suppose if I can I will).

What I was asking in my post was how to avoid just manually indexing 8 times but if my approach is wrong I'd like to know how I could do it better!.

However, changing new_octants to require a FnMut rather than a Fn seems to work perfectly.

This is what I've got now, does it make sense? Does having the index be u8 actually help at all? The index can only be 0-7 so I kind of would like a u3 type so that invalid values can't be represented but then I always end up converting to usize to do any actual indexing.
code:
#[derive(Debug, PartialEq, Copy, Clone)]
pub struct Registered { pub external_id: u32 }
#[derive(Debug, PartialEq, Copy, Clone)]
pub struct Unregistered;

pub trait RegistrationTrait {}
impl RegistrationTrait for Registered {}
impl RegistrationTrait for Unregistered {}

impl<R: RegistrationTrait> SVO<R> {
    pub fn new_octants<F: FnMut(u8) -> SVO<R>>(mut make_octant: F) -> SVO<R> {
        SVO::Octants([
            Box::new(make_octant(0)), Box::new(make_octant(1)),
            Box::new(make_octant(2)), Box::new(make_octant(3)),
            Box::new(make_octant(4)), Box::new(make_octant(5)),
            Box::new(make_octant(6)), Box::new(make_octant(7))])
    }
}

pub trait Deregister: Fn(u32) {}

impl SVO<Registered> {
    fn deregister<D: Deregister>(self, deregister_voxel: &D) -> SVO<Unregistered> {
        match self {
            SVO::Voxel { registration: Registered { external_id }, data } => {
                deregister_voxel(external_id);
                SVO::Voxel { data: data, registration: Unregistered }
            },
            SVO::Octants (mut octants) =>
                SVO::new_octants(|ix: u8| unsafe {
                    mem::replace(&mut octants[ix as usize], mem::uninitialized()).deregister(deregister_voxel)
                })
        }
    }
}

gonadic io fucked around with this message at 10:03 on Apr 21, 2016

syncathetic
Oct 21, 2010
I have an awful suggestion: use mem::transmute to avoid copying altogether. There are a lot of potential problems (it could cause bugs to appear if the structure of SVO changes later), but I don't think there is a faster option.

code:
use std::mem;
use std::marker::PhantomData;

#[derive(Debug, PartialEq, Copy, Clone)]
pub struct Registered;
#[derive(Debug, PartialEq, Copy, Clone)]
pub struct Unregistered;

pub trait RegistrationTrait {}
impl RegistrationTrait for Registered {}
impl RegistrationTrait for Unregistered {}

enum SVO<R: RegistrationTrait> {
    Voxel {
        external_id: u32, // Moved external_id from registration
        data: u32, // Placeholder data type
        phantom: PhantomData<R>,
    },
    Octants([Box<SVO<R>>; 8]),
}


pub trait Deregister: Fn(u32) {}

impl SVO<Registered> {
    fn deregister_inner<D: Deregister>(&self, deregister_voxel: &D) {
        match self {
            &SVO::Voxel { external_id, .. } => deregister_voxel(external_id),
            &SVO::Octants (ref octants) => {
                for c in octants.iter() {
                    c.deregister_inner(deregister_voxel)
                }
            },
        }
    }
    
    pub fn deregister<D: Deregister>(self, deregister_voxel: &D) -> SVO<Unregistered> {
        self.deregister_inner(deregister_voxel);
        unsafe {
            mem::transmute(self)
        }
    }
}

gonadic io
Feb 16, 2011

>>=
What about the best of both worlds?

code:
pub struct Registered { external_id: u32 };
pub struct Unregistered { _dummy: u32 };

enum SVO<R: RegistrationTrait> {
    Voxel {
        registration: R,
        data: VoxelData
    },
    Octants([Box<SVO<R>>; 8]),
}
Now we can still transmute in SVO::register and SVO::deregister, but still get the type-safety of not being able to access external_id in Unregistered voxels.

Urit
Oct 22, 2010
So I'm porting some stuff from Go to Rust to see what it's like, and I'm trying to figure out what the Rust version of this code is. My Go code looks like this:

code:
type Decoder interface {
	Decode(data []byte) (value message.Message, err error)
}

type bindFunc func(c *toml.TomlTree) (Decoder, error)

type registry struct {
	l        sync.RWMutex
	decoders map[string]Decoder
	defaults map[string]Decoder
	binders  map[string]bindFunc
}

// Global static variable
var r = registry{
	decoders: make(map[string]Decoder),
	defaults: make(map[string]Decoder),
	binders:  make(map[string]bindFunc),
}

// One of the functions to hook up a config builder method
func Register(id string, f bindFunc) {
	r.l.Lock()
	r.binders[id] = f
	r.l.Unlock()
}
with a few methods to lock, write to the hashmaps, then unlock, as well as read from them the same way.

I'm trying to figure out how to do this sort of thing in Rust. So far it's been a billion errors about stuff not implementing Sized - apparently that's because traits by themselves aren't assumed to be a pointer to a struct that implements them as in Go, so I have to Box<> them. After I figured that out, it doesn't look like you can make global variables even if you wrap them in a Mutex and I need to use the lazy_static crate, but that doesn't work because Decoder doesn't implement Send so I needed to make my Boxes Box<Decoder+Send>. I'm trying to figure out why this is such a mess and what I'm doing that's so very wrong.

What I'm actually trying to do is make a bunch of struct types that implement a trait called Decoder (e.g. XMLDecoder, JSONDecoder) and then dump the output from a TOML parser into a method to configure each one and return a configured struct instance, then register them by a string name for later reference so I can pass a slice of bytes into the decode method from a bunch of different "reader" type things (e.g. I query a bunch of APIs and pass the result through a different decoder depending on some configuration on the reader side). The decode method never mutates its parent object, it just reads configuration from it, so it should be thread-safe to call decode from a whole bunch of threads at once against the same decoder. The end result is it spits out a JSON-esque enum object (the Message).

I was initially attracted to Rust because of the Enum concept and not having to use interface{} for everything and playing with reflection to see what types are inside the interface{}, but it definitely has a learning curve.

So far what I've got is this:

code:
extern crate toml;

use message::Message;
use std::error::Error;
use std::collections::HashMap;
use std::sync::Mutex;

trait Decoder {
    fn decode(&self, data: Vec<u8>) -> Result<Message, Box<Error>>;
}

type BindFunc = fn(toml::Value) -> Result<Box<Decoder>, Box<Error>>;

struct Registry {
    decoders: Mutex<HashMap<String, Box<Decoder + Send>>>,
    defaults: Mutex<HashMap<String, Box<Decoder + Send>>>,
    binders: Mutex<HashMap<String, BindFunc>>,
}

impl Registry {
    fn new() -> Registry {
        Registry {
            decoders: Mutex::new(HashMap::new()),
            defaults: Mutex::new(HashMap::new()),
            binders: Mutex::new(HashMap::new()),
        }
    }

    pub fn register(&self, name: String, b: BindFunc) {
        let mut bs = self.binders.lock().unwrap();
        let _ = bs.insert(name, b);
    }
}


lazy_static! {
    static ref r:Registry = Registry::new();
}

Urit fucked around with this message at 04:00 on Apr 24, 2016

Ethereal
Mar 8, 2003
I guess my first question is why do you want global state? Can you not get away with passing things around as needed?

Urit
Oct 22, 2010

Ethereal posted:

I guess my first question is why do you want global state? Can you not get away with passing things around as needed?

I don't NEED it to be global (I can just create 1 registry instance and then pass it around somehow I suppose), I just need a single "registry" that the configured structs can get loaded into so I can reference the configured struct from configuration later. I thought about it some more and holy poo poo this is way different not having a garbage collector letting me poo poo objects everywhere.

In the Go code all the stuff registers itself on init e.g.

code:
func init() {
	RegisterDefault("json", &JsonDecoder{})
}
which is similar to a static class constructor in C++ - it's executed once at module load (which is program load in this case).

The config looks something like:

code:
[source.foo]
type = "file"
path = "/x/bar.txt"
decoder = "json"

[decoder.json]
some_option = true
As I said before, regardless of the globalness of the state, what I'm trying to do is at program load, register each type of source, decoder, etc (there's 6 different traits really - source, sink, transformer, decoder, splitter, encoder) with some sort of "registry" struct that maps a string name to a pointer to a struct that implements a trait so that when I go to configure the "foo" source, I can look up the "json" decoder (which is just a struct that implements the Decoder trait, which means I have a "decode" method that can take a byte array and turn it into an object), and map the "decode" method into the source so that every time it grabs a chunk of data it can call that decode method on it.

Also I'm running into lifetime errors trying to get a value out of a map and pass it back to a caller. I guess it makes sense because the map is holding onto that value and the value could be deleted, so then the caller would be referencing freed memory. I guess I have to wrap the whole thing in an Arc<Decoder> instead and clone it for every consumer of the decoder.

Urit fucked around with this message at 08:19 on Apr 24, 2016

syncathetic
Oct 21, 2010
Could you explain what BindFunc and Registery::binders are?

Edit:
I don't totally understand what you're going for, but this is how I would go about implementing a global registry:
code:
#[macro_use]
extern crate lazy_static;

extern crate toml;

//use message::Message;
struct Message;

use std::error::Error;
use std::collections::HashMap;
use std::sync::RwLock;

trait Decoder: Send + Sync {
    fn decode(&self, data: Vec<u8>) -> Result<Message, Box<Error>>;
}

//type BindFunc = fn(toml::Value) -> Result<Box<Decoder>, Box<Error>>;

struct Registry {
    decoders: HashMap<String, usize>,
    defaults: HashMap<String, usize>,
    //binders: Mutex<HashMap<String, BindFunc>>,

    decoders_cache: Vec<Box<Decoder>>,
}

impl Registry {
    fn new() -> Registry {
        Registry {
            decoders: HashMap::new(),
            defaults: HashMap::new(),
            decoders_cache: Vec::new(),
            //binders: Mutex::new(HashMap::new()),
        }
    }

    pub fn lookup_decoder(&self, name: &str) -> Option<&Decoder> {
        self.decoders.get(name).map(|i| &*self.decoders_cache[*i])
    }

    pub fn lookup_default(&self, name: &str) -> Option<&Decoder> {
        self.defaults.get(name).map(|i| &*self.decoders_cache[*i])
    }

    /*pub fn register(&self, name: String, b: BindFunc) {
        let mut bs = self.binders.lock().unwrap();
        let _ = bs.insert(name, b);
    }*/
}


lazy_static! {
    static ref REGISTRY: RwLock<Registry> = RwLock::new(Registry::new());
}


fn do_nothing(decoder: &Decoder) {
}

fn main() {
    {
        let r = REGISTRY.write().unwrap();
        // Insert decoders here
    }
    {
        let r = REGISTRY.read().unwrap();
        let decoder = r.lookup_decoder("json").unwrap();
        do_nothing(decoder);
    }
}

syncathetic fucked around with this message at 09:05 on Apr 24, 2016

Urit
Oct 22, 2010

syncathetic posted:

Could you explain what BindFunc and Registery::binders are?

Bindfunc takes a TOML parse tree and turns it into a configured struct. It's basically a constructor - I just called it a bindfunc because Go doesn't have OO style class-based constructors and you can't scope a function to a type namespace easily like rust's <whatever>::new() inside the impl block. I was "binding" config values to struct values. A binder for a UDP listener looks like:

code:
func bindUDPListen(c *toml.TomlTree) (Source, error) {
	var errs *multierror.Error

	s := &udpListen{}

	if val, err := util.BindToString(c, "address", true); err == nil {
		if addr, uerr := net.ResolveUDPAddr("udp", val); err == nil {
			s.uaddr = addr
		} else {
			errs = multierror.Append(errs, uerr)
		}
	} else {
		errs = multierror.Append(errs, err)
	}

	s.buffer = make([]byte, maxUDPPacketSize)

	if errs.ErrorOrNil() != nil {
		return nil, errs
	}
	return s, nil
}
and then I register it with the registry as {"udplistener": func pointer to bindUDPListen} so if I need to construct a source of type "udplistener" I know which function to call to do that.

Now, your code: Thanks so much, and that's very similar to what I'm trying to do, but what the heck is decoders_cache and why is it borrowing a deref (&*) in a map call? I am confused as to why I can't just return the reference directly from the map.get() call. Also why are the hashmaps to a "usize" instead of a Box<Decoder>, and how would I insert a decoder into them? The thing is that each decoder itself is responsible for calling Register, though again, I'm not sure how I'd do that in Rust because you can't call functions in an "init" or global context as far as I can tell - maybe via std::sync::Once? I'd have to call a constructor on each decoder and add it to the map, correct?

Maybe I'm just doing this hilariously wrong - given the problem, is there a better way? The problem is: take configuration and build structs from that configuration, then allow a configured struct to reference another configured struct. Assume that ordering of config is not an issue e.g. if struct type A depends on struct type B then all structs of type B will always be configured first. This is basically dependency injection, I think.

Edit:

Got it working: https://gist.github.com/highlyunavailable/f8424d2881e2d7b2d510d114a57ed9c3

It still feels like I'm doing it wrong somehow.

Urit fucked around with this message at 21:52 on Apr 24, 2016

Urit
Oct 22, 2010
Double-posting time! I think I have a better (read: more idiomatic) version now:

https://gist.github.com/highlyunavailable/0dab6e17bbace8fd10fa7c2e2f121d27

It seems like lifetimes are doing what I want - each item in the registry must last as long as the registry itself (there is no possibility of deleting it), so as long as the registry is in scope, I can guarantee that the items will not be deallocated. I'm not sure how this will interact with threads but I will only be filling the registry once in a single thread and the registries themselves will live in the "main" thread (the config will be read single-threaded) and then hopefully I can pass off an immutable reference to the decoder (via get() or default()) to another thread.

I'm still not sure how to actually do the registration but worst case I just have a big old populate function in each module that I manually add each submodule to, or an init function in each submodule that the populate function calls.

Jo
Jan 24, 2005

:allears:
Soiled Meat
EDIT: Got it. :3:

Leaving old buggy playground link for prosperity. For future self, used std::str::SplitWhitespace.

http://is.gd/PMopF8

Jo fucked around with this message at 20:35 on May 5, 2016

giogadi
Oct 27, 2009

Finally getting the chance to fart around with this language. It's really cool so far! One thing that impressed me is the operator overloading - I like that I can define an operator with two different types as the operands and even a third type as the sum type.

sarehu
Apr 20, 2007

(call/cc call/cc)
Yeah it's funny how much effort they go through for that when C++ gets it just by overloading.

Jo
Jan 24, 2005

:allears:
Soiled Meat
I'm bumping my head against a module error. Most of the people online seem to say "modules are confusing" and I agree with that sentiment.

I have the following structure:

code:
\ <myapp>
 | Cargo.toml
 \  <src>
   | app.rs
   | geometry.rs
   | settings.rs
   | main.rs
geometry.rs has the following:

code:
mod geometry {

#[derive(Copy, Clone)]
pub struct Vec2<T> {
        pub x: T,
        pub y: T
}

#[derive(Copy, Clone)]
pub struct Vec3<T> {
        pub x: T,
        pub y: T,
        pub z: T
}
pub typedef Vec2f = Vec2<f64>;
pub typedef Vec3f = Vec3<f64>;

}
When I try to use it in app.rs, I see

code:
src/app.rs:2:5: 2:13 error: unresolved import `geometry`. There is no `geometry` in `???` [E0432]
src/app.rs:2 use geometry;
That happens if I use `use geometry` in app.rs. If I use `mod geometry`, in those places, instead I get

code:
src/app.rs:2:5: 2:13 error: cannot declare a new module at this location
src/app.rs:2 mod geometry;
                 ^~~~~~~~
src/app.rs:2:5: 2:13 note: maybe move this module `app` to its own directory via `app/mod.rs`
src/app.rs:2 mod geometry;
                 ^~~~~~~~
src/app.rs:2:5: 2:13 note: ... or maybe `use` the module `geometry` instead of possibly redeclaring it
src/app.rs:2 mod geometry;
The above also appears if I remove the mod geometry { ... } from geometry.rs.

If I use instead `use geometry::*;` I get this:

code:
src/app.rs:2:5: 2:13 error: unresolved import `geometry::*`. Maybe a missing `extern crate geometry`? [E0432]
src/app.rs:2 use geometry::*;
                 ^~~~~~~~
So I'm kinda' confused about when I should use 'mod' and when I should use 'use'. Online docs don't seem to be helping. Do I _have_ to use a folder with a mod.rs?


EDIT: It looks like the problem is I've got main.rs -> app.rs -> geometry.rs. I can't use the flat mapping with this setup and have to use directories.

My new (messy) directory structure is this:

code:
\ myprogram
||- main.rs
|\ <app>
||- mod.rs
||\ <settings>
|||- mod.rs
||\ <geometry>
|||- mod.rs

main.rs references app.rs and settings.rs (for assorted config details).
app.rs references settings.rs and geom.rs.
settings.rs references geom.rs.

Jo fucked around with this message at 00:53 on May 17, 2016

Vanadium
Jan 8, 2005

Jo posted:

I'm bumping my head against a module error. Most of the people online seem to say "modules are confusing" and I agree with that sentiment.

I have the following structure:

code:
\ <myapp>
 | Cargo.toml
 \  <src>
   | app.rs
   | geometry.rs
   | settings.rs
   | main.rs

For that to work out you want no mod lines anywhere but main.rs, main.rs has mod app; mod geometry; mod settings;, and everybody else has use app; use geometry; use settings; as necessary.

mod items are for defining your crate's tree structure, you pretty much only ever want one mod line per module in your whole crate, probably at the highest level at which you want that module to be used. use items just bring stuff into scope so you don't have to use absolute paths that start with :: everywhere.

Vanadium fucked around with this message at 01:03 on May 17, 2016

Jo
Jan 24, 2005

:allears:
Soiled Meat

Vanadium posted:

For that to work out you want no mod lines anywhere but main.rs, main.rs has mod app; mod geometry; mod settings;, and everybody else has use app; use geometry; use settings; as necessary.

mod items are for defining your crate's tree structure, you pretty much only ever want one mod line per module in your whole crate, probably at the highest level at which you want that module to be used. use items just bring stuff into scope so you don't have to use absolute paths that start with :: everywhere.

Thank you so much! That did it.

gonadic io
Feb 16, 2011

>>=
I'm having quite a lot of trouble with what the types of closures are. I have

code:
use svo::voxel_data::VoxelData;
use nalgebra::Vec3;
use std::marker::PhantomData;

// Sadly trait aliasing doesn't exist yet.
pub trait Register: Fn(Vec3<f32>, i32, VoxelData) -> u32 {}
impl<T: Fn(Vec3<f32>, i32, VoxelData) -> u32> Register for T {}
pub trait Deregister: Fn(u32) {}
impl<T: Fn(u32)> Deregister for T {}

#[derive(Debug, PartialEq, Copy, Clone)]
pub struct Registered { pub external_id: u32 }
#[derive(Debug, PartialEq, Copy, Clone)]
pub struct Unregistered { pub _padding: u32 }
impl Unregistered {
    pub fn new() -> Unregistered { Unregistered { _padding: 0 } }
}

pub trait RegistrationTrait {}
impl RegistrationTrait for Registered {}
impl RegistrationTrait for Unregistered {}

pub struct RegistrationFunctions<T: RegistrationTrait, R: Register, D: Deregister> {
    pub register: Box<R>,
    pub deregister: Box<D>,
    registration_trait: PhantomData<T>
}

impl<R: Register, D: Deregister> RegistrationFunctions<Unregistered, R, D> {
	pub fn blank_registration_functions() -> RegistrationFunctions<Unregistered, R, D> {
		let register_closure = |_: Vec3<f32>, _: i32, _: VoxelData| 0;
		let dereg_closure = |_: u32| {};
		RegistrationFunctions {
			register: Box::new(register_closure),
			deregister: Box::new(dereg_closure),
			registration_trait: PhantomData

		}
	}
}
And the idea is to (eventually) have two ways to construct a RegistrationFunctions struct - either a dummy one for Unregistered, or one has a constructor that takes two external functions to make a Registered one. This way it's possible to construct SVOs polymorphically without caring if they've been registered or not.

My current error is:
code:
expected `svo::registration::RegistrationFunctions<svo::registration::Unregistered, R, D>`,
    found `svo::registration::RegistrationFunctions<_, [closure], [closure]
But I think in general I'm getting quite confused with how function types work. Do I need to be polymorphic here? "impl<R: Register, D: Deregister> RegistrationFunctions<Unregistered, R, D>"

Also I tried to make the functions inside the RegistrationFunctions object be references instead of on the heap but apparently the references to the Fn types might outlive the Fns themselves? Do Fns have a hidden lifetime parameter?

e: to recall, Unregistered has the _padding parameter so that I can potentially transmute between Registered and Unregistered.

gonadic io
Feb 16, 2011

>>=
I reread the closure tutorial and finally grokked the comment about how closures are implemented by the compiler constructing bespoke structs for each one and then implementing the Fn traits.

So after just Boxxing everything, I ended up with
code:
pub struct RegistrationFunctions {
    pub register: Box<Fn(Vec3<f32>, i32, VoxelData) -> Registered>,
    pub deregister: Box<Fn(Registered)>
}

impl RegistrationFunctions {
	pub fn dummy() -> RegistrationFunctions {
		RegistrationFunctions {
			register: Box::new(|_, _, _| Registered { external_id: 0}),
			deregister: Box::new(|_| {})
		}
	}

	pub fn external(
			ext_register: RegisterExtern,
		    ext_deregister: DeregisterExtern) -> RegistrationFunctions {
		RegistrationFunctions {
			register: Box::new(move |origin, depth, data|
				Registered{ external_id: ext_register(origin, depth, data) }),
			deregister: Box::new(move |Registered{external_id}| ext_deregister(external_id))
		}
	}
}
Which works great. I'm not super enthusiastic about allocating 2 closures on the heap for the dummy even though it's completely unnecessary but it's good enough for now.

Vanadium
Jan 8, 2005

Yeah you can't really name closure types so you can't reasonably return them out of generic code, so you always have to be generic over them.

Fundamentally, fn foo<T: Fn()>() -> F can't possibly work because foo promises to be able to return a closure of any type of the caller's choosing, which it obviously can't. The inverse where foo returns a closure of a specific secret type that the caller just has to deal with can't currently be expressed, except with boxing+type erasure it like you're doing.

gonadic io
Feb 16, 2011

>>=

Actually this didn't quite work, and the Boxes required lifetime parameters like so: (otherwise the closures were given static lifetimes)

code:
pub struct RegistrationFunctions<'a> {
    pub register: Box<Fn(Vec3<f32>, i32, VoxelData) -> Registered + 'a>,
    pub deregister: Box<Fn(Registered) + 'a>
}

gonadic io fucked around with this message at 16:10 on May 22, 2016

gonadic io
Feb 16, 2011

>>=
I'm actually running into a real design issue here around FFI:
I'd like to deregister a SVO, changing it's type
code:
impl SVO<Registered> {
    fn deregister(self, registration_fns: &RegistrationFunctions) -> SVO<Unregistered> {
        self.deregister_helper(registration_fns);
        unsafe { mem::transmute(self) }
    }
}
Thus any function that calls deregister must own the SVO, so the signature is
code:
impl SVO<Registered> {
    pub fn set_block(
        self,
        registration_fns: &RegistrationFunctions,
        index: &[u8],
        new_data: VoxelData);
}
But during the FFI, the external opaque pointer is (essentially) a *mut SVO<Registered> and so it seems to be impossible to call set_block on it. Should I just get rid of the type parameter to SVO (having set_blocks take a mutable reference) and just do my best to never forget to register one? I'm not entirely sure how to combine the two concepts and I certainly don't want to deep-copy the SVO in order to change its type.

Jo
Jan 24, 2005

:allears:
Soiled Meat
I'm sorta' stuck on generics and inheritance now. I'm defining `trait Node` which has a few attributes. I'd like to make a Graph struct which has a HashMap <String, Node> in it. Is there any way to make graph accept any mix of types, so long as they implement Node? Box them?

Jo fucked around with this message at 01:22 on Jun 2, 2016

Urit
Oct 22, 2010

Jo posted:

I'm sorta' stuck on generics and inheritance now. I'm defining `trait Node` which has a few attributes. I'd like to make a Graph struct which has a HashMap <String, Node> in it. Is there any way to make graph accept any mix of types, so long as they implement Node? Box them?

Yup, you have to box traits because they're just a pointer to a thing that implements Node, so you don't know what size they are, which means you can't properly know what size they're going to be on the stack, so you have to put them in a Box (on the heap).

Linear Zoetrope
Nov 28, 2011

A hero must cook
E: NM

Linear Zoetrope fucked around with this message at 10:38 on Jun 6, 2016

taqueso
Mar 8, 2004


:911:
:wookie: :thermidor: :wookie:
:dehumanize:

:pirate::hf::tinfoil:
Is there a nice way to initialize an array with values from the concatenation of two constant arrays or slices of constant arrays?

I'd like to do something like this:
code:
const PART1: [u8; 3] = [ 0, 1, 2 ];
const PART2: [u8; 2] = [ 3, 4 ];

fn main() {
    let array = PART1 + PART2; //or (PART1, PART2).concat() or = PART1.concat(PART2) or something
    println!("{:?}", array);
}
The following works. The compiler should optimize away initializing a with [0; 5], right? If I leave a uninitialized, the copy_from_slice lines are in error.
code:
const PART1: [u8; 3] = [ 0, 1, 2 ];
const PART2: [u8; 2] = [ 3, 4 ];

fn main() {
    let mut a: [u8; 5] = [0; 5];
    a[0..3].copy_from_slice(&PART1);
    a[3..5].copy_from_slice(&PART2);
    println!("{:?}", a);
}
I'm using nightly if that matters.

e: #rust says that it isn't possible yet

taqueso fucked around with this message at 18:58 on Jun 6, 2016

Jo
Jan 24, 2005

:allears:
Soiled Meat
I noticed recently that when developing I'm "just getting it to compile". That might mean adding & in places or doing foo.to_string() instead of foo. Am I going to be leaking memory like crazy or painting myself into a corner, or can I assume reasonably correct behavior as long as the code compiles (assuming no logic bugs). Rust's doc makes it seem like leaking memory is acceptable, but I'm in a weird state of wanting to free stuff without really having a way to do so aside from drop().

Linear Zoetrope
Nov 28, 2011

A hero must cook
I mean, there are no guarantees about where memory is freed except that it will always be freed sometime between when it's never referenced again and when it goes out of scope, the compiler is free to optimize around that AFAIK, but it will always be dropped by the time it goes out of scope (except for weird cases involving circular reference counters). I'm not sure what you're really asking about. If you absolutely need to free memory NOW and you can't use scoping to achieve it, that's what drop is for. But yes, you should assume memory is alive until you explicitly call drop or the object goes out of scope.

Also, apparently (&str).to_owned() is faster than (&str).to_string() for some reason.

VikingofRock
Aug 24, 2008




Jsor posted:


Also, apparently (&str).to_owned() is faster than (&str).to_string() for some reason.

No longer true on nightly! Specialization allowed for this to be fixed.

Jo
Jan 24, 2005

:allears:
Soiled Meat

Jsor posted:

I mean, there are no guarantees about where memory is freed except that it will always be freed sometime between when it's never referenced again and when it goes out of scope, the compiler is free to optimize around that AFAIK, but it will always be dropped by the time it goes out of scope (except for weird cases involving circular reference counters). I'm not sure what you're really asking about. If you absolutely need to free memory NOW and you can't use scoping to achieve it, that's what drop is for. But yes, you should assume memory is alive until you explicitly call drop or the object goes out of scope.

Also, apparently (&str).to_owned() is faster than (&str).to_string() for some reason.

I'm just worried about building a heaping pile of poo poo because I'm "just getting it done" instead of taking the time to jump back into the docs and running the analytical route. This is entirely a personal project, so I'm trying to concern myself with more of the architectural aspects than I am with the details of the code. It's a prototype engine based on Glium for Awful Jam next month, if that makes a difference.

Rust is in a strange place for me because it sits right between the managed stuff I've done in Python and Java and the completely manual stuff I've done in C. I feel like I should be calling malloc and free and worrying about & vs * vs [], and the fact that I can just kinda' write whatever, fix the compiler warnings, and have it work means I'm suspicious that I'm missing something fundamental.

Bongo Bill
Jan 17, 2012

Rust's memory management is based on the observation that types aren't the only things you can enforce at compile-time. The work of ensuring memory consistency is still being done; it's just that a large part of that work was put into the compiler, and the part that remains for the end-user to do takes the form of appeasing the compiler.

sarehu
Apr 20, 2007

(call/cc call/cc)
Or the end user could understand what's going on. You should be able to "see the allocations" -- they're as visible as they are in C++.

Bongo Bill
Jan 17, 2012

sarehu posted:

Or the end user could understand what's going on. You should be able to "see the allocations" -- they're as visible as they are in C++.

Well, yes. I only mean that the fact that having a machine checking your work makes it easier.

taqueso
Mar 8, 2004


:911:
:wookie: :thermidor: :wookie:
:dehumanize:

:pirate::hf::tinfoil:
I'd like to be able to create byte literals using a different notation than the standard, for 'drawing' in an array of bytes. This is to allow creation of bitmap fonts in the code. I'd like to be able to do something roughly like this:
Rust code:
pub const MY_BYTES: [u8; 3] = [
    draw!( 'XXXX' ),
    draw!( 'X__X' ),
    draw!( '_XX_' ),
];

assert_eq!(draw!('X_X_'), 0b1010_0000);
assert_eq!(draw!('___X'), 0b0001_0000);

Right now, I am doing this instead:
Rust code:
use self::draw::*;

pub const MY_BYTES: [u8; 3] = [
    XXXX,
    X__X,
    _XX_,
];

#[allow(dead_code)]
mod draw {
    use types::u8;
    pub const ____: u8 = 0x00;
    pub const ___X: u8 = 0x10;
    pub const __X_: u8 = 0x20;
    pub const __XX: u8 = 0x30;
    pub const _X__: u8 = 0x40;
    pub const _X_X: u8 = 0x50;
    pub const _XX_: u8 = 0x60;
    pub const _XXX: u8 = 0x70;
    pub const X___: u8 = 0x80;
    pub const X__X: u8 = 0x90;
    pub const X_X_: u8 = 0xA0;
    pub const X_XX: u8 = 0xB0;
    pub const XX__: u8 = 0xC0;
    pub const XX_X: u8 = 0xD0;
    pub const XXX_: u8 = 0xE0;
    pub const XXXX: u8 = 0xF0;
}
}
But I would eventually like to be able to be able to work with all 8 bits and I don't want to have to define each one manually. Is it possible to do this with a macro?

Linear Zoetrope
Nov 28, 2011

A hero must cook
I don't think this is macro-able, but this is practically the example for compiler plugins in the book (they use Roman Numerals, but it's the same idea of parsing identifiers to make numbers).

taqueso
Mar 8, 2004


:911:
:wookie: :thermidor: :wookie:
:dehumanize:

:pirate::hf::tinfoil:

Jsor posted:

I don't think this is macro-able, but this is practically the example for compiler plugins in the book (they use Roman Numerals, but it's the same idea of parsing identifiers to make numbers).

Thanks for pointing that out, it does seem like a good fit and the right place to do this. And if I understand this right, I would make a plugin library that will function like any other lib within the standard cargo dependency system? Rust is too cool.

Linear Zoetrope
Nov 28, 2011

A hero must cook
I actually wrote it:

code:
#![crate_type="dylib"]
#![feature(plugin_registrar, rustc_private)]

extern crate syntax;
extern crate rustc;
extern crate rustc_plugin;

use syntax::codemap::Span;
use syntax::parse::token;
use syntax::ast::TokenTree;
use syntax::ext::base::{ExtCtxt, MacResult, DummyResult, MacEager};
use syntax::ext::build::AstBuilder;  // trait for expr_usize
use rustc_plugin::Registry;

fn expand_draw(cx: &mut ExtCtxt, sp: Span, args: &[TokenTree])
        -> Box<MacResult + 'static> {

    if args.len() != 1 {
        cx.span_err(
            sp,
            &format!("argument should be a single identifier, but got {} arguments", args.len()));
        return DummyResult::any(sp);
    }

    let text = match args[0] {
        TokenTree::Token(_, token::Ident(s, _)) => s.to_string(),
        _ => {
            cx.span_err(sp, "argument should be a single identifier");
            return DummyResult::any(sp);
        }
    };

    if text.len() > 8 {
    	cx.span_err(sp, 
    		&format!("argument is interpreted as bits of a u8, a string of size {} overflows u8.", text.len()));
    	return DummyResult::any(sp);
    }

    let mut text = &*text;
    let mut total: u8 = 0;
    while !text.is_empty() {
    	// Do this at the start, it won't affect the total
    	// the first time, but if we do it after we do it
    	// once too many times
    	total >>= 1;

        if text.ends_with('X') {
        	total |= 0b1000_0000;

        } else if text.ends_with('_') {
        	// Do nothing, this is valid, but worth 0
        } else {
        	cx.span_err(sp, "argument should only contain X's and _'s, e.g. XX_X");
        	return DummyResult::any(sp);
        }
        text = &text[0..text.len()-1]
    }

    MacEager::expr(cx.expr_usize(sp, total as usize))
}

#[plugin_registrar]
pub fn plugin_registrar(reg: &mut Registry) {
    reg.register_macro("draw", expand_draw);
}
I tried to conform to your test cases which were a bit unintuitive. It seems like you want X to denote a 1, and _ to denote a 0, and anything "missing" to automatically be 0. You start describing from the left, so

XXX = 0b1110_0000
X_ = 0b1000_0000
XXXXXX_X = 0b1111_1101

Right?

What I wrote passes all of your test cases, if I got it wrong I at least gave you a point to start from.

As a note, if you instead treat everything as starting from the rightmost bytes (that is XXXX is 0000_1111 instead of 1111_0000) you can trivially extend this to an arbitrary usize, but the way you wrote it makes that much harder.

Linear Zoetrope fucked around with this message at 01:43 on Jul 9, 2016

taqueso
Mar 8, 2004


:911:
:wookie: :thermidor: :wookie:
:dehumanize:

:pirate::hf::tinfoil:
I wrote it too. :hf: It was really easy, since that example was almost perfect. https://github.com/jdeeny/drawbytes

This needs to follow the Chip8 defacto standard for the fonts, so it needs to be the most significant bits ('left justified'). I was mulling over how to make it a little more configurable, maybe an optional format string, like 'u8L' or 'u32R'.

When I run cargo test on the library that uses the plugin, I get an error during the doc-test phase. It would be nice to fix this.

e: I had to add plugin = true to the lib section of the plugin's Cargo.toml


taqueso fucked around with this message at 03:54 on Jul 9, 2016

taqueso
Mar 8, 2004


:911:
:wookie: :thermidor: :wookie:
:dehumanize:

:pirate::hf::tinfoil:
Rust code:
    fn store(&mut self, dest: Dest, data: usize) -> Chip8Result<()> {
        match dest {
            Dest::Register(r) => if r < self.v.len() { self.v[r] = data as MemoryCell & 0xFF; Ok(()) } else { Err(Chip8Error::OutOfBounds(r)) },
            ...
        }
    }
    fn load(&mut self, src: Src) -> Chip8Result<usize> {
        match src {
            Src::Register(r)    => if r < self.v.len() { Ok(self.v[r] as usize) } else { Err(Chip8Error::OutOfBounds(r)) },
            ...
        }
    }
Is there a better way to turn an vector index into a result?

e: I found get and get_mut in the docs, so I can use
code:
            Dest::Register(r) => self.v.get_mut(r).map(|reg| {*reg = data as Register8 & 0xFF; () }).ok_or(Chip8Error::OutOfBoundsAt(r)),
...
            Src::Register(r)  => self.v.get(r).map(|reg| *reg as usize).ok_or(Chip8Error::OutOfBoundsAt(r)),
I'm not sure if that is better.

taqueso fucked around with this message at 06:45 on Jul 12, 2016

QuantumNinja
Mar 8, 2013

Trust me.
I pretend to be a ninja.
I'd write it more like:

code:
            Dest::Register(r) => self.v.get_mut(r)
                                       .and_then(|reg| { *reg = data as Register8 & 0xFF; Some(())} )
                                       .ok_or(Err(Chip8Error::OutOfBoundsAt(r))),
...
            Src::Register(r)  => self.v.get(r)
                                       .and_then(|reg| *reg as usize)
                                       .ok_or(Chip8Error::OutOfBoundsAt(r)),
That said, the Dest register thing seems a bit like a misuse of the Result type. Do you plan to catch and handle that Chip8Error? If not, use a panic!. If you do, a Result containing a unit on one side is an Option, and it would probably be nicer to just write update_reg(reg: Reg, index: usize) -> Option<Chip8Error> and call that.

QuantumNinja fucked around with this message at 06:13 on Jul 28, 2016

Adbot
ADBOT LOVES YOU

taqueso
Mar 8, 2004


:911:
:wookie: :thermidor: :wookie:
:dehumanize:

:pirate::hf::tinfoil:

QuantumNinja posted:

I'd write it more like:

code:
            Dest::Register(r) => self.v.get_mut(r)
                                       .and_then(|reg| { *reg = data as Register8 & 0xFF; Some(())} )
                                       .ok_or(Err(Chip8Error::OutOfBoundsAt(r))),
...
            Src::Register(r)  => self.v.get(r)
                                       .and_then(|reg| *reg as usize)
                                       .ok_or(Chip8Error::OutOfBoundsAt(r)),
That said, the Dest register thing seems a bit like a misuse of the Result type. Do you plan to catch and handle that Chip8Error? If not, use a panic!. If you do, a Result containing a unit on one side is an Option, and it would probably be nicer to just write update_reg(reg: Reg, index: usize) -> Option<Chip8Error> and call that.

I don't want to panic inside my library for a minor error like this. I'm trying to pass the results all the way out to the API boundary so the calling app can see & handle the errors. I considered using Option here, but didn't use it for 2 reasons - I read somewhere that it was bad form to use None to indicate an error condition, and also so I don't have to convert the Option to a Result in the calling functions. This is pretty analogous to std's use of None when popping from a collection, so it is mostly for the second reason.

The library is pretty functional now, I'll try to post the code later today.

  • 1
  • 2
  • 3
  • 4
  • 5
  • Post
  • Reply