Here's my attempt at categorising them. I've tried to cover the most significant differences between these constructs in different languages, and I've tried to choose language-agnostic terms; but those are somewhat subjective considerations, so take it with a grain of salt. This will also necessarily be biased towards constructs in languages I'm familiar with.
Probably the simplest composite data type; it consists of a set of fields, each of which has a name and a type. The only things you can do with them are construct them (by providing values for each field), and read or write fields by name. Generally, all field accesses are static, meaning it's known at compile-time which field an access is bound to; you can't do things like
some_field is itself a variable, at least not unless the language supports some kind of reflection.
Generally, structs are value types, but languages which support them usually allow taking references to them too; and they are nominal types, so two struct types with the same field names and types aren't compatible.
Structs may have functions associated with them, which might look like methods if the language supports Uniform Function Call Syntax (UFCS). However, I do not classify them as methods since they are not dynamically dispatched based on the subject's runtime type.
Tuples (product types)
These are similar to structs: the most obvious difference is that the fields are numbered (or ordered) rather than named. However, there are a few more subtle differences.
Tuple types are almost always value types, and structural types, meaning that two tuple types are compatible if their respective component types are. They may also be anonymous types. A tuple type may be representable as e.g.
S * T or
(S, T) where
T are themselves types; this means that generic functions can operate on tuples in ways that they would not be able to on structs.
I don't know of any languages besides Rust which allow associated functions and UFCS with tuple types.
In many contexts a record is a synonym for a struct. However, at least to me the term "record" has a few different connotations to term "struct". For one, records might be comparable for equality (by comparing their fields) by default, and they are normally used for things where such comparison would make sense. If two record instances happened to have equal fields despite not logically being equal (in the domain), then normally the programmer would add an explicit "identity" or "ID" field in order to differentiate them. So records are value types but they are often used to model things which have identities.
Record types might be either nominal or structural. If they are structural, then there is probably a subtyping relationship between records which have strictly more fields, and possibly when the type of a field in one record is a subtype of that field's type in another record.
There are several major distinctions between classes and structs, mostly relating to the fundamental principles of Object-Oriented Programming. Like structs, they have named fields, and field accesses are (usually) either statically bound, or at least refer to statically-known field names.
The most significant feature of classes is that they can have methods. The difference between a function and a method is that a method can be overridden, so there can be multiple implementations of a method, and when the method is invoked, it is dynamically dispatched (i.e. an implementation is selected at runtime) based on the subject's runtime type.
Classes also have constructors or initialisers ─ special methods (or method-like members) which are invoked when the class is instantiated. Whereas structs can be constructed as data by just providing their field values, class constructors might provide their own values for some or all of the class's fields, or perform other behaviour.
Generally, classes are reference types ─ instances with equal fields still have different identities ─ and they are nominal types.* They can be extended by subclasses, which inherit members (fields and methods) from their superclasses. (This could be single or multiple inheritance.) Class inheritance also implies subtyping: subclasses are subtypes of their superclasses.
Particularly in statically typed languages, there is likely to be some kind of visibility feature for class members, allowing them to be declared as public or private (or possibly other visibilities).
* I don't know of any languages besides Typescript in which classes are structurally typed. Even in Typescript, structural compatibility of two classes unrelated by inheritance is almost always unintended, and a source of potential bugs.
Class<?> instances in Java, which represent the class but are a different language construct distinct from the class itself.
Dynamic classes are usually mutable: they can be monkey-patched, changing the behaviour of their instances, often even instances which had already been constructed before the monkey-patch was made. New dynamic classes can be declared at runtime, and they might extend other dynamic classes without necessarily knowing at "compile-time" which other class they extend.
These are similar to classes, in that they can declare fields and methods, and can inherit members or be inherited from. The difference is that a mixin cannot be constructed directly, and it might not even actually be a type (rather, it is only a blueprint for a set of members).
Mixins may be a separate language construct from classes in order to provide a mechanism for multiple inheritance, if the language only allows single inheritance for classes. In this case, a class can inherit from any number of mixins but may only (directly) extend one other class.
They are always reference types. They might support inheritance from a dynamic class or a prototype, or not at all. In the latter case, they are constructed by directly listing their members. It is often possible to access fields dynamically, e.g.
some_field is itself a variable, or to iterate over an object's members, without needing a special API for reflection.
Dynamic objects only exist in dynamically-typed languages, so duck typing with them is common. Such languages sometimes later introduce an optional static or gradual type system, but these are generally not powerful enough to describe every way that dynamic objects are used in practice.
An interface is a data type that is not a data structure. Typically it is declared similarly to a class, but all of its members are dynamically dispatched. For example, in a language where class field accesses are statically bound, interfaces probably would not be allowed to declare fields.
Unlike concrete data types which can be instantiated directly, interfaces are implemented by other types (particularly classes), so to get a value of an interface type you must construct a value of some concrete type which implements the interface. Interfaces may be nominally or structurally typed; i.e. the language may or may not require you to declare explicitly which interfaces are implemented by a class.
An interface which declares methods generally does not provide implementations of them; those are instead provided by implementing classes.
These are quite similar to interfaces, in that they are data types but not data structures, they are implemented by other types, and the trait declaration generally doesn't provide implementations for its own members. However, at least to me a "trait" does not necessarily mean that its members must be dynamically dispatched, and this means traits can be implemented by structs or other types which don't have any dynamically dispatched members.
Still, since a trait may be implemented by multiple types, this means that when a trait "method" is invoked, an implementation needs to be selected. This could be either by monomorphisation of generic functions at compile-time, or by dynamic dispatch at runtime (Rust, for example, supports both).