Both observations are true: create_connection is a coroutine function that returns a transport, protocol pair. But to get a hold of that return value, you need to await the coroutine (if in an async def) or use loop.run_until_complete (if in synchronous code).
If you just call a coroutine function without awaiting the result, what you get is a coroutine object that you can pass around and await later. This is analogous to how just calling a generator function just returns a generator-iterator; to actually get values out of it, you must exhaust it with a for loop.
Many examples don't really need the return value, so they only store the returned coroutine object in a local variable coro and then run run_until_complete(coro), effectively discarding the returned transport/protocol pair. The full usage is shown for example in 19.5.4.3.5:
connect_coro = loop.create_connection(MyProtocol, sock=rsock)
transport, protocol = loop.run_until_complete(connect_coro)
I believe much of the confusion would disappear if that and other examples were formulated along the lines of:
transport, protocol = loop.run_until_complete(
loop.create_connection(MyProtocol, sock=rsock))
That makes it clearer that create_connection returns a pair, and that you need run_until_complete (the sync equivalent of await) to access it, the latter being the case with every coroutine or asyncio future.