So imagine you created yourself a vertex struct as so:

// using repr(C) so we know how rust will lay this out in memory
// (aka it follows C's rules and does no reordering of fields)

struct Vertex {
    // So our first field position, consists of 3 floats, our x,y and z co-ordinates
    position: [f32; 3],
    // Our second field holds color, consists of 3 floats again, r,g and b in this case
    color: [f32; 3]

When this struct is in memory, it should look as so:

Each "cell" is a byte in memory

Offset: | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10| 11| 12| 13| 14| 15| 16| 17| 18| 19| 20| 21| 22| 23|
Value:  | x | x | x | x | y | y | y | y | z | z | z | z | r | r | r | r | g | g | g | g | b | b | b | b |


Now sometimes compilers might add padding at the end so that when the struct sits in an array, it's first element has the best alignment for processing from the CPU, so size_of(Vertex) may not be 24 it could be more, due to 0 being padded on the end.

And depending on the types of variables used, they might not necessarily be packed as tightly (There can be padding between fields to meet alignment requirements).

But in this case we should be fine.

wgpu::VertexBufferDescriptor {
    // size_of gives you the size of the struct, including any compiler added padding, so we can just do this
    // to give our number of bytes to move per vertex
    stride: std::mem::size_of::<Vertex>(),
    // we indeed do want to step through the vertexes in here one at a time
    step_mode: StepMode::Vertex,
    // Ok so now we need to inform the graphics api of the attributes, or fields of struct that are relevant.
    attributes: &[
        // So first is our position attribute
        VertexAttributeDescriptor {
            // The start of which is at the start of our vertex in terms of memory
            offset: 0,
            // we want the graphics api to treat the data there as a 3 component vector (the maths kind) where each component is an f32
            // ( or in glsl speak, this value is of type vec3 )
            format: VertexFormat::Float3,
            // when you create vertex shaders, you'll specify variables to hold this attribute, and you'll assign a location number in 
            // your shader source to them. So here we are saying we want our position to be assign location number 0 (see example glsl below)
            shader_location: 0
        // And now our color attribute
        VertexAttributeDescriptor {
            // So the start of our color, looking at the memory layout above, is the first byte for
            // our red component, so 12.
            // but just to do this nicely, we'll make use of the memory functions just to make sure we
            // are right.
            // so we want to be 3 floats in (3 * 4 = 12)
            offset: std::mem::size_of::<f32>() * 3,
            // again this is vec3
            format: VertexFormat::Float3,
            // and we want this at a different shader input location.
            shader_location: 1

Example glsl of a vertex shader

#version 450

layout(location = 0) in vec3 a_position;
layout(location = 1) in vec3 a_color;

// in and out have their own location slots, so doesn't matter we have two zero's here.
// we have an out here to pass the data on to fragment shader, which would have it's 
// own in variables, with locations matching the outs defined here

// though we'll get to that when we get to shaders
layout(location = 0) out vec3 v_color;

void main() {
    // gl_Position is the variable we set to instruct the graphics card of
    // our finally agreed position on the screen
    gl_Position = a_position;
    // since v_color is an out, this'll get passed to the fragment shader.
    // The value will also automatically be interpolated by the graphics card
    // for points between vertices
    v_color = a_color;