7

I'm making an OOP chat client in Rust. The module messages.rs creates and handles messages to other modules as structs: SimpleMessage and ComplexMessage structs:

//! # Messages

use time::SteadyTime;

/// Represents a simple text message
pub struct SimpleMessage<'a> {
    pub user: ...
    pub time: &'a SteadyTime<'a>,
    pub content: &'a str,
}

/// Represents attachments, like text or multimedia files.
pub struct ComplexMessage<'a> {
    pub user: ...
    pub time: &'a SteadyTime<'a>,
    //pub content: PENDING
}

impl<'a> SimpleMessage<'a> { }
impl<'a> ComplexMessage<'a> { }

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn is_simple() {
        assert_eq!(&self.instance_of(), SimpleMessage);
    }

    #[test]
    fn is_complex() {
        assert_eq!(&self.instance_of(), ComplexMessage);
    }
}

I'm troubled finding a Java-like function such as InstanceOf() for structs, that would potentially work like:

&self.instance_of() -> str

This would be used to process a ComplexMessage different from a SimpleMessage in a GUI, adding a preview and download button for the ComplexMessage.

Any ideas?

Dialvive
  • 356
  • 7
  • 19
  • 5
    Why would you need this, given Rust's compile time guarantees? – jhpratt Aug 24 '18 at 13:26
  • 2
    Please review how to create a [MCVE] and then [edit] your question to include it. We cannot tell what types, fields, etc. are present in the code, nor can we see *why* you want to accomplish this goal or what code would require it. Try to produce something that reproduces your error on the [Rust Playground](https://play.rust-lang.org) or you can reproduce it in a brand new Cargo project. There are [Rust-specific MCVE tips](//stackoverflow.com/tags/rust/info) as well. – Shepmaster Aug 24 '18 at 13:31
  • 2
    Perhaps [How to match trait implementors](https://stackoverflow.com/q/26126683/155423) will help – Shepmaster Aug 24 '18 at 13:35
  • Your updated code does not contain a definition for `User`; does it even need to have a user at all to show the problem? Most importantly, it doesn't show the code explaining why you *need* an `instanceof` check in the first place. It just shows that you want to call it. – Shepmaster Aug 24 '18 at 14:00
  • 2
    Are you already aware that [using `instanceof` in Java is not a good idea](https://stackoverflow.com/questions/30894002/is-it-good-practice-to-often-use-instanceof)? Copying bad design from one language to the other is not usually an encouraged path. – Shepmaster Aug 24 '18 at 14:01
  • Hard time writing a first-time question in StackOverflow haha, thank you @Shepmaster – Dialvive Aug 24 '18 at 14:06
  • 1
    *Hard time writing a first-time question* — I note from your profile that you are a CS student, so hopefully this unsolicited advice isn't unwelcomed: the feedback that we are providing you with is absolutely not limited to Stack Overflow. It will be pertinent to every part of your entire professional life. Reducing a problem to it's bare essentials ("create a MCVE"), evaluating *why* you are doing something, taking the time to format and frame your communication, etc. are all critical to success (in programming and likely most everything). – Shepmaster Aug 24 '18 at 14:12
  • Any advice is more than welcomed @Shepmaster ! "crate" changed to "module", thanks. – Dialvive Aug 24 '18 at 14:33

1 Answers1

13

First of all, if you try to port Java OOP idioms to Rust you are going to have a hard time. Rust programmers use completely different idioms and patterns, which are more suited to the design of the language.

That said, you can compare types using std::any::TypeId. A similar function to instanceOf can be implemented like this:

use std::any::{Any, TypeId};

trait InstanceOf
where
    Self: Any,
{
    fn instance_of<U: ?Sized + Any>(&self) -> bool {
        TypeId::of::<Self>() == TypeId::of::<U>()
    }
}

// implement this trait for every type that implements `Any` (which is most types)
impl<T: ?Sized + Any> InstanceOf for T {}

And use it like this:

let msg = ComplexMessage::new();

println!("msg is ComplexMessage: {}", msg.instance_of::<ComplexMessage>());
println!("msg is SimpleMessage: {}", msg.instance_of::<SimpleMessage>());

Outputs:

msg is ComplexMessage: true
msg is SimpleMessage: false

Note that Rust does not have a concept of type inheritance like Java does, so this will only tell you if it is exactly the same type.


A more Rusty approach to your problem, as DK commented below this answer, would be to use an enum to model the fact that you have two kinds of message. Rust enums are nothing like Java enums - they are just as powerful as structs except they model the idea of alternatives, as opposed to aggregates. Here is one way you could implement this using the types you have and wrapping them up:

enum Message<'a> {
    Complex(ComplexMessage<'a>),
    Simple(SimpleMessage<'a>),
}

Whenever a function can only accept a ComplexMessage you can write the signature to reflect that:

fn send_multimedia(msg: ComplexMessage) { ... }

And whenever you can accept either type, use the enum:

fn get_msg_size(msg: Message) -> usize {
    match(msg) {
        Message::Complex(complex) => complex.content.len() + complex.file.size(),
        Message::Simple(simple) => simple.content.len(),
    }
}
Peter Hall
  • 53,120
  • 14
  • 139
  • 204
  • Does your initial solution using `instance_of` work also if `msg` is a reference with a trait type, and if its value is some unknown trait implementation, either `SimpleMessage` or `ComplexMessage`? That is the essence of the Java `instanceof` operator, that the runtime type of the argument is tested. In your given example `msg` seems to have the non-reference type `ComplexMessage`. An `instanceof` operator which only works in that case is nothing like the one that is found in Java, and that should be clearly stated in the answer text. – Lii Aug 24 '18 at 14:07
  • I also thought about using a Struct-tuple, haven't given a thought for enums though, using them may be a more elegant (and much useful) solution, let me try it out, thanks DK. @Shepmaster – Dialvive Aug 24 '18 at 14:10
  • @Lii No it won't work in that situation. It's literally only going to match exact types. – Peter Hall Aug 24 '18 at 14:15
  • 2
    Wouldn't [`Any::is`](https://doc.rust-lang.org/std/any/trait.Any.html#method.is) be a shorter implementation? – Shepmaster Aug 24 '18 at 14:17
  • 1
    @Shepmaster Meh, it limits the types to be `Sized`. May not be a problem... – Peter Hall Aug 24 '18 at 14:30
  • 1
    Thanks @PeterHall using enums it is the most elegant/practical solution. – Dialvive Aug 24 '18 at 14:51