As Ethernet uses MAC addresses for communication, could I create an Ethernet network where devices would not have an IP address, just a MAC address?
If you were writing all your own software from scratch, then you could certainly do this. Just have the software accept a MAC address anywhere that the normal counterpart to that program would have accepted an IP address. Use all the system calls to send raw ethernet packets rather than IP address and it will work - but it would be a huge hassle.
Generally, MAC addresses on your network do not follow any pattern. They are burned into the hardware by the manufacturer. They are long and bulky. Mine right now is C8-60-00-CA-4B-9A. The computer next to me is 00-40-F4-48-1B-88.
To have the machines be able to talk you each other, you could give each machine a hard-coded list of all the MAC addresses of all the other machines on the network so it would know where to send packets. This is a lot of error prone typing, and anytime you changed your any networking hardware you would have to go around and change all the lists to reflect the new MAC addresses.
This is a huge hassle, so you'd probably end up coming up with a way for the machines on the network to automatically discover each others' MAC addresses using broadcast packets. Then you'd give them a way to identify themselves with some meaningful address so you would have to type commands like "telnet C8-60-00-CA-4B-9A".
Turns out that this is exactly what IP does- it is a way to use meaningful numbers to address hosts on a network rather than hard coding MAC addresses. Add in DNS on top of IP and you can type command like "telnet webserver".
Couldn't Ethernet make use of IP addresses to send messages? I'm not saying it should, I'm just asking if it could have chosen to do so.
MAC addresses are 6 bytes of info and IP addresses are only 4 bytes, so you can't do any kind of 1 to 1 mapping. You need some way of finding the MAC address (to put in the packet) from an IP address (supplied by the software that wants to communicate with another host on the network).
One (hard core) way to do this would be to go into every machine on the network, and change its hardware MAC address to look like an IP address by making the top two bytes be zeros (or some other fixed number that is the same for every machine on the network) and set the bottom four bytes to the "IP address" you want them to have on the network. (Most network cards will let you go in and modify the vendor assigned MAC address)
To make this actually work, next you would also have to hack up the code in your networking stack to actually use this system. You'd basically rip out everything having to do with ARP (the method IP uses to translate IP addresses into MAC address). You'd rip out the parts that build/read IP headers. Instead, you'd replace it all with the very simple code that, given an IP packet to be sent to host at address w.x.y.z, build an ethernet frame with the DEST address set to 00-00-w-x-y-z.
You would also need a way to indicate to the receiver of a packet which protocol (UDP, TCP) it is meant for. You could probably stick this somewhere in the ethernet header by overriding an existing field. Maybe use one of the top two bytes of the source address? This would not effect the destination machines ability to receive, but could mess up some switches. You could also add the protocol to the beginning or end of the Ethernet frame and increase the payload size by one - but that is starting to smell like an IP header.
So what would all this work buy you?
First it would save you the overhead of a lookup in the ARP table on every outgoing packet. This is probably on the order of only microseconds.
You save the work of computing IP header checksums, and the memory needed to hold them. This is probably not significant on modern hardware.
You save 16 bytes in every packet on the network since there would be no IP headers. This could add up depending on the application.
The biggest gain would be that you would not have to do any ARP requests. Sending a standard IP packet to a new host triggers an ARP exchange that can take milliseconds and is unpredictable. This can be a huge gain for some applications that are very sensitive to latency and jitter.
For some very specialized applications this actually makes sense to do. I once worked a real-time system that used only broadcast UDP packets for all inter host communications for the sole reason that it avoided having those ARP sequences kick in and unpredictably add delay and jitter. I also once worked on a resource-limited embedded system that worked by sending UDP payloads inside IP packets directly (no IP header) because it saved all the complexity and memory needed to implement all the ARP and netmask and extra checksums stuff.