Str makes my struct Unsized?

I’m inventing a simple struct inheritance by composing the base struct within a Box<SubStructData>, which can hold different struct types. E.g., if element.kind == TEXTAREA, then element.data = box as Box<TextAreaData>.

I’ve no problem with inheritance, but rather with the data my Boxed<SubStructData>'ses will contain. I.e., right now I can’t compile the coming code due to the reason:

the trait bound str: std::marker::Sized is not satisfied in TextAreaData

The code:

use std::boxed::Box;

fn main() {
    let tx = Element::new_textarea("Hellou!");
    println!("{:?} {:?}", tx.x, tx.as_textarea().unwrap().text); // 0, "Hellou!"
}

struct Element {
    kind: u8,
    x: f32,
    y: f32,
    elem: Box<ElementData>
}

impl Element {
    fn new_textarea(txt: str) -> Element {
        Element { kind: 0x80, elem: Box::new(TextAreaData { text: txt }), x: 0.0, y: 0.0 }
    }

    fn as_textarea(&self) -> Option<TextAreaData> {
        return if self.kind == 0x80 {
            Some(self.elem.downcast::<TextAreaData>())
        } else {
            None
        }
    }
}

trait ElementData {}

struct TextAreaData {
    text: str
}

impl ElementData for TextAreaData {}

So now I can’t have a str in my boxes, too? I’ve tried boxing it, + Sized-ing it too and I still got strange errors. I did try further in those attempts, too.

Did I just miss a syntax for making str sized?

P.s., full log:

  --> src/main.rs:20:5
   |
20 | /     fn as_textarea(&self) -> Option<TextAreaData> {
21 | |         return if self.kind == 0x80 {
22 | |             Some(self.elem.downcast::<TextAreaData>())
23 | |         } else {
24 | |             None
25 | |         }
26 | |     }
   | |_____^ `str` does not have a constant size known at compile-time
   |
   = help: within `TextAreaData`, the trait `std::marker::Sized` is not implemented for `str`
   = note: required because it appears within the type `TextAreaData`
   = note: required by `std::option::Option`

You need either

struct TextAreaData {
    text: String
}

Or

struct TextAreaData<'a> {
    text: &'a str
}
1 Like

Well, I’ve tried the second one, but it’s conflicting some Rust requirements. I declare lifetime 'k in many places (due to inherits) and I get

error[E0495]: cannot infer an appropriate lifetime for lifetime parameter `'k` due to conflicting requirements
  --> src/main.rs:17:9
   |
17 |         Element { kind: 0x80, elem: Box::new(TextAreaData { text: txt }), x: 0.0, y: 0.0 }
   |         ^^^^^^^
   |
note: first, the lifetime cannot outlive the lifetime 'k as defined on the impl at 15:1...
  --> src/main.rs:15:1
   |
15 | impl<'k> Element<'k> {
   | ^^^^^^^^^^^^^^^^^^^^
   = note: ...so that the expression is assignable:
           expected Element<'k>
              found Element<'_>
   = note: but, the lifetime must be valid for the static lifetime...
   = note: ...so that the expression is assignable:
           expected std::boxed::Box<ElementData<'_> + 'static>
              found std::boxed::Box<ElementData<'_>>

error[E0599]: no method named `downcast` found for type `std::boxed::Box<ElementData<'k> + 'static>` in the current scope
  --> src/main.rs:22:28
   |
22 |             Some(self.elem.downcast::<TextAreaData>())
   |                            ^^^^^^^^

I cannot understand this entirely, it looks alright. o_o

use std::boxed::Box;

fn main() {
    let tx = Element::new_textarea("Hellou!");
    println!("{:?} {:?}", tx.x, tx.as_textarea().unwrap().text); // 0, "Hellou!"
}

struct Element<'k> {
    kind: u8,
    x: f32,
    y: f32,
    elem: Box<ElementData<'k>>
}

impl<'k> Element<'k> {
    fn new_textarea(txt: &'k str) -> Element {
        Element { kind: 0x80, elem: Box::new(TextAreaData { text: txt }), x: 0.0, y: 0.0 }
    }

    fn as_textarea(&self) -> Option<TextAreaData> {
        return if self.kind == 0x80 {
            Some(self.elem.downcast::<TextAreaData>())
        } else {
            None
        }
    }
}

trait ElementData<'k> {}

struct TextAreaData<'k> {
    text: &'k str
}

impl<'k> ElementData<'k> for TextAreaData<'k> {}

This won’t end up well because Any (to use Box::downcast) requires 'static. But the String version is likely what’s desired anyway.

But, that said, I think an enum may model the domain better (from what I can gather here) than a trait.

1 Like

If you didn’t need downcast, the right way to specify the lifetime region over which the trait object is valid is:

struct Element<'k> {
    kind: u8,
    x: f32,
    y: f32,
    elem: Box<ElementData + 'k> // + 'k says the underlying object would be valid over the 'k lifetime if it stays alive that long
}

and remove the generic lifetime parameter from the ElementData trait.

&str is a reference to a string. str is the actual content of the string. If you have &str referencing "hello world", then this str is 11 bytes large. Or 3 bytes large if it’s "foo", etc.

struct TextAreaData {
    text: str
}

This would make TextAreaData as large as number of bytes in the string, and because the string could have any length, TextAreaData could have any (unknown at compile time) length too.