Synthesize GUI from serde / Debug?

  1. There is two separate questions, one is synthesizing something that is Read-Only. One is synthesizing something that is Read-Write. I am interested in solutions to both.

  2. For this example, I will be writing as if the output is HTML, but this is not an actual requirement of the problem.

So here is the problem statement. Suppose we have something like:

pub struct Person {
  name: string,
  age: usize,
  knows_rust: bool

standard Debug allows us to print this char out to a String. However, suppose we wanted to print this out to HTML, something like:

<div> Person
<span><span>Name:</span> </span><br>
<span><span>Age:</span> self.age </span><br>
<span><span>Knows_Rust: </span> self.knows_rust</span><br>

suppose furthermore something that was read-write, i.e.

<div> Person
<span><span>Name:</span> <input default-value=""> </span><br>
<span><span>Age:</span> <ihnput default-valued="self.age"> </span><br>
<span><span>Knows_Rust: </span> <input type="checkbox" default-value ="self.knows_rust"></span><br>

with proper hooks setup to be notified on every change.

Question: is there an existing crate for doing things like this ? (Output does not have to be HTML; if the output is some other GUI library, that is fine, HTML is just used as an example).

EDIT: Preferred "gui format" would actually be OpenGL, but easier to describe via HTML.

EDIT2: Non serde solutions welcome too. I think Serde certainly has all the info needed, but solutions not involving serde welcome too.

1 Like

Just implement your own custom trait, perhaps? You probably won't make much use of the serde, as it assumes that your data will be serializable in one way only. Something like this could work:

pub struct Person {
  name: String,
  age: usize,
  knows_rust: bool

pub trait ReadOnlyHTML {
    fn read_only(&self) -> String;

pub trait ReadWriteHTML {
    fn read_write(&self) -> String;

impl ReadOnlyHTML for Person {
    fn read_only(&self) -> String {
<span>Name:</span> {} </span><br>
<span><span>Age:</span> {} </span><br>
<span><span>Knows_Rust: </span> {} </span><br>
<.div>"#,, self.age, self.knows_rust)

impl ReadWriteHTML for Person {
    fn read_write(&self) -> String {
        format!(r#"<div> Person
<span><span>Name:</span> <input default-value="{}"> </span><br>
<span><span>Age:</span> <ihnput default-valued="{}"> </span><br>
<span><span>Knows_Rust: </span> <input type="checkbox" default-value ="{}"></span><br>
<.div>"#,, self.age, self.knows_rust)

fn main() {
    let p = Person {
        name: "Mike".to_string(),
        knows_rust: false,
        age: 31,
    println!("Readable:\n\n{}\n", p.read_only());
    println!("Writable:\n\n{}\n", p.read_write());

Adding any sort of hooks makes it much less trivial, though - do you need to re-render your text on any change? Then the listeners for your changes will have to play not with the outputs from the functions themselves, but with the data delivered to them by whomever happens to handle the change. Giving your listeners a handle to the receiving end of the Watch channel, for instance, can help here.

We do not have the same problem in mind. The power of serde/Debug is that you can #[derive ...] them. I want to be able to write:

pub struct T { ... }

and have it auto generate a way to display the struct in a GUI.

Then, I want to be able to write

#[derive(GuiOut, GuiIn)]
pub struct T { ... }

and now the GUI is also editable, allowing me to modify the underlying struct.

The core of the difficulty is not hand writing the code you wrote above, but having a procedural macro generate it.

This is why serde may be relevant -- the GuiOut/GuiIn code needs to do similar types of tree walking on the struct/enums.

Doesn't sound that complicated, as long as you're willing to get into the core of procedural macros - I'm not an expert on them, and never got to learn using them as I dislike the idea of creating multiple crates just to hold the helper utilities I need, but you'll definitely be able to do what you want to do with them.

You'll have to think about the concrete implementation of your hooks either way beforehand, though - so I don't see how much sense it makes to start thinking from the end, without any consideration for what the underlying generated code will do you for you. Figure out how you'll want your hooks to behave and what will be used for the communication between the data changers and the listeners for those changers, then abstract away into a #[derive(...)] or something else.

I have written procedural macros before, know for a fact that creating a serde-like library, despite simple sounding, is complicated (parsing structs / enums have all types of weird gotcha cases), which is why the original question asks about existing crates:

It's kind of hard to help when the exact use case isn't particularly clear, though.

There are many ways in which all sorts of hooks can be created - can you give an example of how you're planning to use the generated data and its implementations? Knowing what you want to type is great, knowing what you actually want to happen behind the hood is a bit better.

The question you are asking is hard to answer because the answer depends on the GUI platform. However, I don't really care about the GUI platform -- if anyone has solved this problem for any GUI platform, I am happy to study that crate.

The way to think about this problem is as follows:

#[derive(Debug)] is nice if you are printing things out to a screen; but even better, for GUI apps, would be if we could dump the data out as HTML / VisualBasic Form / some GFX format.

Then, at this point, this problem shifts to: you know, it would be kind of cool if this same UI for displaying the struct also allowed us to modify the struct / could be used as a way for the user to create instances of the struct. Thus, the "write" portion.


At this point here, you are insisting that I provide more details because the above is vague. And I agree, the above is vague.

I am hoping that someone else has run into this precise problem, had this series of thoughts, and released a crate for solving this problem.

That's all.

I am in no way claiming that I have a fully spec-ed out idea. What I present here is a train of thoughts and asking if anyone has built a crate along these ideas.

1 Like

Still a bit too vague to suggest anything useful, but I think I get the idea. After playing a bit with macro_rules!, which I happen to be much more familiar with, here's how your original example might be implemented for any generic struct with any number of fields with usable types:

macro_rules! ComponentUI {
    $name:ident as 
    $(R $read_marker:tt)? 
    $(W $write_marker:tt)? {
      $( $field:ident : $type:ty )*
  ) => {
    pub struct $name {
      pub $( $field: $type ),*
    impl std::fmt::Display for $name {
      fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {

        let readable = (vec![ $(stringify!($read_marker))? ] 
          as Vec<&str>).join("").len() > 0;

        if !readable { return write!(f, 
          "{} is not Readable", stringify!($name)); }

        let writable = (vec![ $(stringify!($write_marker))? ] 
          as Vec<&str>).join("").len() > 0;

        let div = vec![
          format!("<div> {}", stringify!($name)),
              { // "name" -> "Name"
                let (first, rest) = stringify!($field).split_at(1);
                format!("{}{}", first.to_uppercase(), rest)
              if writable {
                format!("<input default-value='{}' {}>", self.$field, 
                  match stringify!($type) {
                    "String" => "type='text'",
                    "u8" | "u32" => "type='number'",
                    "bool" => "type='checkbox'",
                    _ => ""
              } else {
                format!("{}", self.$field)
        write!(f, "{}", div.join("\n"))

Usable as:

  Person as R|W ? {
    rustacean: bool
    experience: u8
    name: String
    age: u32


<div> Person
<span><span>Rustacean:</span><input default-value='true' type='checkbox'></span><br>
<span><span>Experience:</span><input default-value='64' type='number'></span><br>
<span><span>Name:</span><input default-value='Guy' type='text'></span><br>
<span><span>Age:</span><input default-value='32' type='number'></span><br>

Each individual implementation for each GUI you'll use will have to implement things differently, and I sincerely doubt there's going to be one crate to "rule them all" in this aspect. I'm still fairly confused about your concept of hooks and what exactly you were planning to do with them, but hopefully this will give a head start as to where you might begin to work things out.

1 Like

This is not a derive Macro. It prevents me from adding other derives to the struct, like #[derive(Clone, Eq, ...)]. It also doesn't handle pub struct Foo(usize). It also does not handle generics. It also does not handle tuples / enums. It probably does unpleasant things to IDEs / code completion too.

After fixing all this, you probably have something that has a similar structure to serde, in that it 'tree walks' structs / enums.

I don't know what you are trying to prove with posting toy examples in comments when I am asking if people know of a production quality crate I can use.

I doubt there’s a crate for this. GUI’s in general are usually pretty specific to a project in order to get the presentation that you want. Therefore, the crate would have to have a lot of customization capability in order to be useful to a lot of people which would make the crate quite complicated to write. I think your best bet if you want to try it is to try implementing a Serializer for html. Getting a basic one working wouldn’t be too much work and then you can evaluate whether it’s worth the effort to try and handle all your use cases. For deserialization, I’d imagine there’s some sort of serde compatible form-encoded deserialization crate already.

That is an interesting point. I should go look at Rust libraries for doing CRUD apps. They probably have very similar problem with "rust Struct <-> editable form".

You could use a crate like serde-value or unstructured to serialize your data to a simpler enum format and the use that to generate your UI. The UI could then modify that intermediate value and if necessary deserialize it back to the original type. This might be simpler than writing a custom serde serializer.

1 Like

Is this basically the same as:

We have rust struct Foo

  serde: Foo -> Json
  have routines for drawing Json

  have UI for modifying Json
  serde: Json -> Result<Foo, ...>

Except that instead of using Json, we use a slightly more friendly format? Is that the basic idea ?

Yes, exactly. The crates I linked would hopefully be more efficient than serializing to JSON but otherwise work pretty much the same. You could maybe also look at their source code if you wanted to write a custom serializer yourself.

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.